| /* |
| * Copyright (C) 2019 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 <netdb.h> |
| |
| #include <array> |
| #include <atomic> |
| #include <chrono> |
| #include <ctime> |
| #include <span> |
| #include <thread> |
| |
| #include <android-base/logging.h> |
| #include <android/multinetwork.h> |
| #include <arpa/inet.h> |
| #include <cutils/properties.h> |
| #include <gmock/gmock-matchers.h> |
| #include <gtest/gtest.h> |
| #include <netdutils/NetNativeTestBase.h> |
| |
| #include "Experiments.h" |
| #include "resolv_cache.h" |
| #include "resolv_private.h" |
| #include "stats.h" |
| #include "tests/dns_responder/dns_responder.h" |
| #include "tests/resolv_test_utils.h" |
| |
| using namespace std::chrono_literals; |
| |
| using android::netdutils::IPSockAddr; |
| |
| const std::string kMaxCacheEntriesFlag("persist.device_config.netd_native.max_cache_entries"); |
| |
| constexpr int TEST_NETID_2 = 31; |
| constexpr int DNS_PORT = 53; |
| |
| // Constant values sync'd from res_cache.cpp |
| constexpr int DNS_HEADER_SIZE = 12; |
| constexpr int MAX_ENTRIES_DEFAULT = 64 * 2 * 5; |
| constexpr int MAX_ENTRIES_LOWER_BOUND = 1; |
| constexpr int MAX_ENTRIES_UPPER_BOUND = 100 * 1000; |
| |
| namespace { |
| |
| struct CacheEntry { |
| std::vector<uint8_t> query; |
| std::vector<uint8_t> answer; |
| }; |
| |
| struct SetupParams { |
| std::vector<std::string> servers; |
| std::vector<std::string> domains; |
| res_params params; |
| aidl::android::net::ResolverOptionsParcel resolverOptions; |
| std::vector<int32_t> transportTypes; |
| bool metered; |
| }; |
| |
| struct CacheStats { |
| SetupParams setup; |
| std::vector<res_stats> stats; |
| int pendingReqTimeoutCount; |
| }; |
| |
| std::vector<uint8_t> makeQuery(int op, const char* qname, int qclass, int qtype) { |
| uint8_t buf[MAXPACKET] = {}; |
| const int len = res_nmkquery(op, qname, qclass, qtype, {}, buf, /*netcontext_flags=*/0); |
| return std::vector<uint8_t>(buf, buf + len); |
| } |
| |
| std::vector<uint8_t> makeAnswer(const std::vector<uint8_t>& query, const char* rdata_str, |
| const unsigned ttl) { |
| test::DNSHeader header; |
| header.read(reinterpret_cast<const char*>(query.data()), |
| reinterpret_cast<const char*>(query.data()) + query.size()); |
| |
| for (const test::DNSQuestion& question : header.questions) { |
| std::string rname(question.qname.name); |
| test::DNSRecord record{ |
| .name = {.name = question.qname.name}, |
| .rtype = question.qtype, |
| .rclass = question.qclass, |
| .ttl = ttl, |
| }; |
| test::DNSResponder::fillRdata(rdata_str, record); |
| header.answers.push_back(std::move(record)); |
| } |
| |
| char answer[MAXPACKET] = {}; |
| char* answer_end = header.write(answer, answer + sizeof(answer)); |
| return std::vector<uint8_t>(answer, answer_end); |
| } |
| |
| // Get the current time in unix timestamp since the Epoch. |
| time_t currentTime() { |
| return std::time(nullptr); |
| } |
| |
| // Comparison for res_sample. |
| bool operator==(const res_sample& a, const res_sample& b) { |
| return std::tie(a.at, a.rtt, a.rcode) == std::tie(b.at, b.rtt, b.rcode); |
| } |
| |
| // Comparison for res_stats. |
| bool operator==(const res_stats& a, const res_stats& b) { |
| if (std::tie(a.sample_count, a.sample_next) != std::tie(b.sample_count, b.sample_next)) { |
| return false; |
| } |
| for (int i = 0; i < a.sample_count; i++) { |
| if (a.samples[i] != b.samples[i]) return false; |
| } |
| return true; |
| } |
| |
| // Comparison for res_params. |
| bool operator==(const res_params& a, const res_params& b) { |
| return std::tie(a.sample_validity, a.success_threshold, a.min_samples, a.max_samples, |
| a.base_timeout_msec, a.retry_count) == |
| std::tie(b.sample_validity, b.success_threshold, b.min_samples, b.max_samples, |
| b.base_timeout_msec, b.retry_count); |
| } |
| |
| } // namespace |
| |
| class ResolvCacheTest : public NetNativeTestBase { |
| protected: |
| static constexpr res_params kParams = { |
| .sample_validity = 300, |
| .success_threshold = 25, |
| .min_samples = 8, |
| .max_samples = 8, |
| .base_timeout_msec = 1000, |
| .retry_count = 2, |
| }; |
| |
| ResolvCacheTest() { |
| // Store the default one and conceal 10000+ lines of resolver cache logs. |
| defaultLogSeverity = android::base::SetMinimumLogSeverity( |
| static_cast<android::base::LogSeverity>(android::base::WARNING)); |
| } |
| ~ResolvCacheTest() { |
| cacheDelete(TEST_NETID); |
| cacheDelete(TEST_NETID_2); |
| |
| // Restore the log severity. |
| android::base::SetMinimumLogSeverity(defaultLogSeverity); |
| } |
| |
| [[nodiscard]] bool cacheLookup(ResolvCacheStatus expectedCacheStatus, uint32_t netId, |
| const CacheEntry& ce, uint32_t flags = 0) { |
| int anslen = 0; |
| std::vector<uint8_t> answer(MAXPACKET); |
| const auto cacheStatus = resolv_cache_lookup(netId, ce.query, answer, &anslen, flags); |
| if (cacheStatus != expectedCacheStatus) { |
| ADD_FAILURE() << "cacheStatus: expected = " << expectedCacheStatus |
| << ", actual =" << cacheStatus; |
| return false; |
| } |
| |
| if (cacheStatus == RESOLV_CACHE_FOUND) { |
| answer.resize(anslen); |
| if (answer != ce.answer) { |
| ADD_FAILURE() << "The answer from the cache is not as expected."; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| int cacheCreate(uint32_t netId) { |
| return resolv_create_cache_for_net(netId); |
| } |
| |
| void cacheDelete(uint32_t netId) { |
| resolv_delete_cache_for_net(netId); |
| } |
| |
| int cacheAdd(uint32_t netId, const CacheEntry& ce) { |
| return resolv_cache_add(netId, ce.query, ce.answer); |
| } |
| |
| int cacheAdd(uint32_t netId, const std::vector<uint8_t>& query, |
| const std::vector<uint8_t>& answer) { |
| return resolv_cache_add(netId, query, answer); |
| } |
| |
| int cacheGetExpiration(uint32_t netId, const std::vector<uint8_t>& query, time_t* expiration) { |
| return resolv_cache_get_expiration(netId, query, expiration); |
| } |
| |
| void cacheQueryFailed(uint32_t netId, const CacheEntry& ce, uint32_t flags) { |
| _resolv_cache_query_failed(netId, ce.query, flags); |
| } |
| |
| int cacheSetupResolver(uint32_t netId, const SetupParams& setup) { |
| return resolv_set_nameservers(netId, setup.servers, setup.domains, setup.params, |
| setup.resolverOptions, setup.transportTypes, setup.metered); |
| } |
| |
| void cacheAddStats(uint32_t netId, int revision_id, const IPSockAddr& ipsa, |
| const res_sample& sample, int max_samples) { |
| resolv_cache_add_resolver_stats_sample(netId, revision_id, ipsa, sample, max_samples); |
| } |
| |
| int cacheFlush(uint32_t netId) { return resolv_flush_cache_for_net(netId); } |
| |
| bool isUidAllowedBypassPrivateDnsOnNetwork(int netid, unsigned uid) { |
| return resolv_is_uid_allowed_bypass_private_dns_on_network(netid, uid); |
| } |
| |
| int setAllowBypassPrivateDnsOnNetwork(int netid, unsigned uid, bool allowed) { |
| return resolv_set_allow_bypass_private_dns_on_network(netid, uid, allowed); |
| } |
| |
| void expectCacheStats(const std::string& msg, uint32_t netId, const CacheStats& expected) { |
| int nscount = -1; |
| sockaddr_storage servers[MAXNS]; |
| int dcount = -1; |
| char domains[MAXDNSRCH][MAXDNSRCHPATH]; |
| res_stats stats[MAXNS]; |
| res_params params = {}; |
| int res_wait_for_pending_req_timeout_count; |
| android_net_res_stats_get_info_for_net(netId, &nscount, servers, &dcount, domains, ¶ms, |
| stats, &res_wait_for_pending_req_timeout_count); |
| |
| // Server checking. |
| EXPECT_EQ(nscount, static_cast<int>(expected.setup.servers.size())) << msg; |
| for (int i = 0; i < nscount; i++) { |
| EXPECT_EQ(ToString(&servers[i]), expected.setup.servers[i]) << msg; |
| } |
| |
| // Domain checking |
| EXPECT_EQ(dcount, static_cast<int>(expected.setup.domains.size())) << msg; |
| for (int i = 0; i < dcount; i++) { |
| EXPECT_EQ(std::string(domains[i]), expected.setup.domains[i]) << msg; |
| } |
| |
| // res_params checking. |
| EXPECT_TRUE(params == expected.setup.params) << msg; |
| |
| // res_stats checking. |
| if (expected.stats.size() == 0) { |
| for (int ns = 0; ns < nscount; ns++) { |
| EXPECT_EQ(0U, stats[ns].sample_count) << msg; |
| } |
| } |
| for (size_t i = 0; i < expected.stats.size(); i++) { |
| EXPECT_TRUE(stats[i] == expected.stats[i]) << msg; |
| } |
| |
| // wait_for_pending_req_timeout_count checking. |
| EXPECT_EQ(res_wait_for_pending_req_timeout_count, expected.pendingReqTimeoutCount) << msg; |
| } |
| |
| CacheEntry makeCacheEntry(int op, const char* qname, int qclass, int qtype, const char* rdata, |
| std::chrono::seconds ttl = 10s) { |
| CacheEntry ce; |
| ce.query = makeQuery(op, qname, qclass, qtype); |
| ce.answer = makeAnswer(ce.query, rdata, static_cast<unsigned>(ttl.count())); |
| return ce; |
| } |
| |
| private: |
| android::base::LogSeverity defaultLogSeverity; |
| }; |
| |
| TEST_F(ResolvCacheTest, CreateAndDeleteCache) { |
| // Create the cache for network 1. |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| EXPECT_EQ(-EEXIST, cacheCreate(TEST_NETID)); |
| EXPECT_TRUE(has_named_cache(TEST_NETID)); |
| |
| // Create the cache for network 2. |
| EXPECT_EQ(0, cacheCreate(TEST_NETID_2)); |
| EXPECT_EQ(-EEXIST, cacheCreate(TEST_NETID_2)); |
| EXPECT_TRUE(has_named_cache(TEST_NETID_2)); |
| |
| // Delete the cache in network 1. |
| cacheDelete(TEST_NETID); |
| EXPECT_FALSE(has_named_cache(TEST_NETID)); |
| EXPECT_TRUE(has_named_cache(TEST_NETID_2)); |
| } |
| |
| // Missing checks for the argument 'answer'. |
| TEST_F(ResolvCacheTest, CacheAdd_InvalidArgs) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| |
| const std::vector<uint8_t> queryEmpty(MAXPACKET, 0); |
| const std::vector<uint8_t> queryTooSmall(DNS_HEADER_SIZE - 1, 0); |
| CacheEntry ce = makeCacheEntry(QUERY, "valid.cache", ns_c_in, ns_t_a, "1.2.3.4"); |
| |
| EXPECT_EQ(-EINVAL, cacheAdd(TEST_NETID, queryEmpty, ce.answer)); |
| EXPECT_EQ(-EINVAL, cacheAdd(TEST_NETID, queryTooSmall, ce.answer)); |
| |
| // Cache not existent in TEST_NETID_2. |
| EXPECT_EQ(-ENONET, cacheAdd(TEST_NETID_2, ce)); |
| } |
| |
| TEST_F(ResolvCacheTest, CacheAdd_DuplicateEntry) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| CacheEntry ce = makeCacheEntry(QUERY, "existent.in.cache", ns_c_in, ns_t_a, "1.2.3.4"); |
| time_t now = currentTime(); |
| |
| // Add the cache entry. |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ce)); |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce)); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_FOUND, TEST_NETID, ce)); |
| |
| // Get the expiration time and verify its value is greater than now. |
| time_t expiration1; |
| EXPECT_EQ(0, cacheGetExpiration(TEST_NETID, ce.query, &expiration1)); |
| EXPECT_GT(expiration1, now); |
| |
| // Adding the duplicate entry will return an error, and the expiration time won't be modified. |
| EXPECT_EQ(-EEXIST, cacheAdd(TEST_NETID, ce)); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_FOUND, TEST_NETID, ce)); |
| time_t expiration2; |
| EXPECT_EQ(0, cacheGetExpiration(TEST_NETID, ce.query, &expiration2)); |
| EXPECT_EQ(expiration1, expiration2); |
| } |
| |
| TEST_F(ResolvCacheTest, CacheLookup) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| EXPECT_EQ(0, cacheCreate(TEST_NETID_2)); |
| CacheEntry ce = makeCacheEntry(QUERY, "existent.in.cache", ns_c_in, ns_t_a, "1.2.3.4"); |
| |
| // Cache found in network 1. |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ce)); |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce)); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_FOUND, TEST_NETID, ce)); |
| |
| // No cache found in network 2. |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID_2, ce)); |
| |
| ce = makeCacheEntry(QUERY, "existent.in.cache", ns_c_in, ns_t_aaaa, "2001:db8::1.2.3.4"); |
| |
| // type A and AAAA are independent. |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ce)); |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce)); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_FOUND, TEST_NETID, ce)); |
| } |
| |
| TEST_F(ResolvCacheTest, CacheLookup_CacheFlags) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| std::vector<char> answerFromCache; |
| CacheEntry ce = makeCacheEntry(QUERY, "existent.in.cache", ns_c_in, ns_t_a, "1.2.3.4"); |
| |
| // The entry can't be found when only no-cache-lookup bit is carried. |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ce, ANDROID_RESOLV_NO_CACHE_LOOKUP)); |
| |
| // Ensure RESOLV_CACHE_SKIP is returned when there's no such the same entry in the cache. |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_SKIP, TEST_NETID, ce, ANDROID_RESOLV_NO_CACHE_STORE)); |
| |
| // Skip the cache lookup if no-cache-lookup and no-cache-store bits are carried |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_SKIP, TEST_NETID, ce, |
| ANDROID_RESOLV_NO_CACHE_LOOKUP | ANDROID_RESOLV_NO_CACHE_STORE)); |
| |
| // Add the cache entry. |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ce)); |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce)); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_FOUND, TEST_NETID, ce)); |
| |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ce, ANDROID_RESOLV_NO_CACHE_LOOKUP)); |
| |
| // Now no-cache-store has no effect if a same entry is existent in the cache. |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_SKIP, TEST_NETID, ce, ANDROID_RESOLV_NO_CACHE_STORE)); |
| |
| // Skip the cache lookup again regardless of a same entry being already in the cache. |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_SKIP, TEST_NETID, ce, |
| ANDROID_RESOLV_NO_CACHE_LOOKUP | ANDROID_RESOLV_NO_CACHE_STORE)); |
| } |
| |
| TEST_F(ResolvCacheTest, CacheLookup_Types) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| static const struct QueryTypes { |
| int type; |
| std::string rdata; |
| } Types[] = { |
| {ns_t_a, "1.2.3.4"}, |
| {ns_t_aaaa, "2001:db8::1.2.3.4"}, |
| {ns_t_ptr, "4.3.2.1.in-addr.arpa."}, |
| {ns_t_ptr, "4.0.3.0.2.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa."}, |
| }; |
| |
| for (const auto& t : Types) { |
| std::string name = fmt::format("cache.lookup.type.{}", t.rdata); |
| SCOPED_TRACE(name); |
| |
| CacheEntry ce = makeCacheEntry(QUERY, name.data(), ns_c_in, t.type, t.rdata.data()); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ce)); |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce)); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_FOUND, TEST_NETID, ce)); |
| } |
| } |
| |
| TEST_F(ResolvCacheTest, CacheLookup_InvalidArgs) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| |
| const std::vector<uint8_t> queryEmpty(MAXPACKET, 0); |
| const std::vector<uint8_t> queryTooSmall(DNS_HEADER_SIZE - 1, 0); |
| std::vector<uint8_t> answerTooSmall(DNS_HEADER_SIZE - 1, 0); |
| const CacheEntry ce = makeCacheEntry(QUERY, "valid.cache", ns_c_in, ns_t_a, "1.2.3.4"); |
| auto cacheLookupFn = [](const std::vector<uint8_t>& query, |
| std::vector<uint8_t> answer) -> ResolvCacheStatus { |
| int anslen = 0; |
| return resolv_cache_lookup(TEST_NETID, query, answer, &anslen, 0); |
| }; |
| |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce)); |
| |
| EXPECT_EQ(RESOLV_CACHE_UNSUPPORTED, cacheLookupFn(queryEmpty, ce.answer)); |
| EXPECT_EQ(RESOLV_CACHE_UNSUPPORTED, cacheLookupFn(queryTooSmall, ce.answer)); |
| EXPECT_EQ(RESOLV_CACHE_UNSUPPORTED, cacheLookupFn(ce.query, answerTooSmall)); |
| |
| // It can actually be found with valid arguments. |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_FOUND, TEST_NETID, ce)); |
| |
| // Cache not existent in TEST_NETID_2. |
| EXPECT_EQ(-ENONET, cacheAdd(TEST_NETID_2, ce)); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_UNSUPPORTED, TEST_NETID_2, ce)); |
| } |
| |
| TEST_F(ResolvCacheTest, CacheLookup_Expired) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| |
| // An entry with zero ttl won't be stored in the cache. |
| CacheEntry ce = makeCacheEntry(QUERY, "expired.in.0s", ns_c_in, ns_t_a, "1.2.3.4", 0s); |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce)); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ce)); |
| |
| // Create an entry expired in 1s. |
| ce = makeCacheEntry(QUERY, "expired.in.1s", ns_c_in, ns_t_a, "1.2.3.4", 1s); |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce)); |
| |
| // Cache found. |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_FOUND, TEST_NETID, ce)); |
| time_t expiration; |
| EXPECT_EQ(0, cacheGetExpiration(TEST_NETID, ce.query, &expiration)); |
| |
| // Wait for the cache expired. |
| std::this_thread::sleep_for(1500ms); |
| EXPECT_GE(currentTime(), expiration); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ce)); |
| } |
| |
| TEST_F(ResolvCacheTest, PendingRequest_QueryDeferred) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| EXPECT_EQ(0, cacheCreate(TEST_NETID_2)); |
| |
| CacheEntry ce = makeCacheEntry(QUERY, "query.deferred", ns_c_in, ns_t_a, "1.2.3.4"); |
| std::atomic_bool done(false); |
| |
| // This is the first lookup. The following lookups from other threads will be in the |
| // pending request list. |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ce)); |
| |
| std::vector<std::thread> threads(5); |
| for (std::thread& thread : threads) { |
| thread = std::thread([&]() { |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_FOUND, TEST_NETID, ce)); |
| |
| // Ensure this thread gets stuck in lookups before we wake it. |
| EXPECT_TRUE(done); |
| }); |
| } |
| |
| // Wait for a while for the threads performing lookups. |
| // TODO: Perhaps implement a test-only function to get the number of pending requests |
| // instead of sleep. |
| std::this_thread::sleep_for(100ms); |
| |
| // The threads keep waiting regardless of any other networks or even if cache flag is set. |
| EXPECT_EQ(0, cacheAdd(TEST_NETID_2, ce)); |
| cacheQueryFailed(TEST_NETID, ce, ANDROID_RESOLV_NO_CACHE_STORE); |
| cacheQueryFailed(TEST_NETID, ce, ANDROID_RESOLV_NO_CACHE_LOOKUP); |
| cacheQueryFailed(TEST_NETID_2, ce, ANDROID_RESOLV_NO_CACHE_STORE); |
| cacheQueryFailed(TEST_NETID_2, ce, ANDROID_RESOLV_NO_CACHE_LOOKUP); |
| cacheDelete(TEST_NETID_2); |
| |
| // Ensure none of the threads has finished the lookups. |
| std::this_thread::sleep_for(100ms); |
| |
| // Wake up the threads |
| done = true; |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce)); |
| |
| for (std::thread& thread : threads) { |
| thread.join(); |
| } |
| } |
| |
| TEST_F(ResolvCacheTest, PendingRequest_QueryFailed) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| |
| CacheEntry ce = makeCacheEntry(QUERY, "query.failed", ns_c_in, ns_t_a, "1.2.3.4"); |
| std::atomic_bool done(false); |
| |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ce)); |
| |
| std::vector<std::thread> threads(5); |
| for (std::thread& thread : threads) { |
| thread = std::thread([&]() { |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ce)); |
| |
| // Ensure this thread gets stuck in lookups before we wake it. |
| EXPECT_TRUE(done); |
| }); |
| } |
| |
| // Wait for a while for the threads performing lookups. |
| std::this_thread::sleep_for(100ms); |
| |
| // Wake up the threads |
| done = true; |
| cacheQueryFailed(TEST_NETID, ce, 0); |
| |
| for (std::thread& thread : threads) { |
| thread.join(); |
| } |
| } |
| |
| TEST_F(ResolvCacheTest, PendingRequest_CacheDestroyed) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| EXPECT_EQ(0, cacheCreate(TEST_NETID_2)); |
| |
| CacheEntry ce = makeCacheEntry(QUERY, "query.failed", ns_c_in, ns_t_a, "1.2.3.4"); |
| std::atomic_bool done(false); |
| |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ce)); |
| |
| std::vector<std::thread> threads(5); |
| for (std::thread& thread : threads) { |
| thread = std::thread([&]() { |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ce)); |
| |
| // Ensure this thread gets stuck in lookups before we wake it. |
| EXPECT_TRUE(done); |
| }); |
| } |
| |
| // Wait for a while for the threads performing lookups. |
| std::this_thread::sleep_for(100ms); |
| |
| // Deleting another network must not cause the threads to wake up. |
| cacheDelete(TEST_NETID_2); |
| |
| // Ensure none of the threads has finished the lookups. |
| std::this_thread::sleep_for(100ms); |
| |
| // Wake up the threads |
| done = true; |
| cacheDelete(TEST_NETID); |
| |
| for (std::thread& thread : threads) { |
| thread.join(); |
| } |
| } |
| |
| TEST_F(ResolvCacheTest, MaxEntries) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| std::vector<CacheEntry> ces; |
| const int max_cache_entries = resolv_get_max_cache_entries(TEST_NETID); |
| |
| for (int i = 0; i < 2 * max_cache_entries; i++) { |
| std::string qname = fmt::format("cache.{:06d}", i); |
| SCOPED_TRACE(qname); |
| CacheEntry ce = makeCacheEntry(QUERY, qname.data(), ns_c_in, ns_t_a, "1.2.3.4"); |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce)); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_FOUND, TEST_NETID, ce)); |
| ces.emplace_back(ce); |
| } |
| |
| for (int i = 0; i < 2 * max_cache_entries; i++) { |
| std::string qname = fmt::format("cache.{:06d}", i); |
| SCOPED_TRACE(qname); |
| if (i < max_cache_entries) { |
| // Because the cache is LRU, the oldest queries should have been purged, |
| // and the most recent max_cache_entries ones should still be present. |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ces[i])); |
| } else { |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_FOUND, TEST_NETID, ces[i])); |
| } |
| } |
| } |
| |
| TEST_F(ResolvCacheTest, CacheFull) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| |
| CacheEntry ce1 = makeCacheEntry(QUERY, "cache.000000", ns_c_in, ns_t_a, "1.2.3.4", 100s); |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce1)); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_FOUND, TEST_NETID, ce1)); |
| |
| CacheEntry ce2 = makeCacheEntry(QUERY, "cache.000001", ns_c_in, ns_t_a, "1.2.3.4", 1s); |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce2)); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_FOUND, TEST_NETID, ce2)); |
| |
| // Stuff the resolver cache. |
| const int max_cache_entries = resolv_get_max_cache_entries(TEST_NETID); |
| for (int i = 2; i < max_cache_entries; i++) { |
| std::string qname = fmt::format("cache.{:06d}", i); |
| SCOPED_TRACE(qname); |
| CacheEntry ce = makeCacheEntry(QUERY, qname.data(), ns_c_in, ns_t_a, "1.2.3.4", 50s); |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce)); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_FOUND, TEST_NETID, ce)); |
| } |
| |
| // Wait for ce2 expired. |
| std::this_thread::sleep_for(1500ms); |
| |
| // The cache is full now, and the expired ce2 will be removed first. |
| CacheEntry ce3 = makeCacheEntry(QUERY, "cache.overfilled.1", ns_c_in, ns_t_a, "1.2.3.4", 50s); |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce3)); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_FOUND, TEST_NETID, ce3)); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ce2)); |
| |
| // The cache is full again but there's no one expired, so the oldest ce1 will be removed. |
| CacheEntry ce4 = makeCacheEntry(QUERY, "cache.overfilled.2", ns_c_in, ns_t_a, "1.2.3.4", 50s); |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce4)); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_FOUND, TEST_NETID, ce4)); |
| EXPECT_TRUE(cacheLookup(RESOLV_CACHE_NOTFOUND, TEST_NETID, ce1)); |
| } |
| |
| class ResolvCacheParameterizedTest : public ResolvCacheTest, |
| public testing::WithParamInterface<int> {}; |
| |
| INSTANTIATE_TEST_SUITE_P(MaxCacheEntries, ResolvCacheParameterizedTest, |
| testing::Values(MAX_ENTRIES_LOWER_BOUND - 1, MAX_ENTRIES_UPPER_BOUND + 1), |
| [](const testing::TestParamInfo<int>& info) { |
| return std::to_string(info.param); |
| }); |
| |
| TEST_P(ResolvCacheParameterizedTest, IrrationalCacheSize) { |
| // Assign an out-of-bounds value. |
| ScopedSystemProperties sp1(kMaxCacheEntriesFlag, std::to_string(GetParam())); |
| android::net::Experiments::getInstance()->update(); |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| EXPECT_EQ(MAX_ENTRIES_DEFAULT, resolv_get_max_cache_entries(TEST_NETID)); |
| } |
| |
| TEST_F(ResolvCacheTest, ResolverSetup) { |
| const SetupParams setup = { |
| .servers = {"127.0.0.1", "::127.0.0.2", "fe80::3"}, |
| .domains = {"domain1.com", "domain2.com"}, |
| .params = kParams, |
| }; |
| |
| // Failed to setup resolver because of the cache not created. |
| EXPECT_EQ(-ENONET, cacheSetupResolver(TEST_NETID, setup)); |
| EXPECT_FALSE(resolv_has_nameservers(TEST_NETID)); |
| |
| // The cache is created now. |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| EXPECT_EQ(0, cacheSetupResolver(TEST_NETID, setup)); |
| EXPECT_TRUE(resolv_has_nameservers(TEST_NETID)); |
| } |
| |
| TEST_F(ResolvCacheTest, ResolverSetup_InvalidNameServers) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| const std::string invalidServers[]{ |
| "127.A.b.1", |
| "127.^.0", |
| "::^:1", |
| "", |
| }; |
| SetupParams setup = { |
| .servers = {}, |
| .domains = {"domain1.com"}, |
| .params = kParams, |
| }; |
| |
| // Failed to setup resolver because of invalid name servers. |
| for (const auto& server : invalidServers) { |
| SCOPED_TRACE(server); |
| setup.servers = {"127.0.0.1", server, "127.0.0.2"}; |
| EXPECT_EQ(-EINVAL, cacheSetupResolver(TEST_NETID, setup)); |
| EXPECT_FALSE(resolv_has_nameservers(TEST_NETID)); |
| } |
| } |
| |
| TEST_F(ResolvCacheTest, ResolverSetup_DropDomain) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| |
| // Setup with one domain which is too long. |
| const std::vector<std::string> servers = {"127.0.0.1", "fe80::1"}; |
| const std::string domainTooLong(MAXDNSRCHPATH, '1'); |
| const std::string validDomain1(MAXDNSRCHPATH - 1, '2'); |
| const std::string validDomain2(MAXDNSRCHPATH - 1, '3'); |
| SetupParams setup = { |
| .servers = servers, |
| .domains = {}, |
| .params = kParams, |
| }; |
| CacheStats expect = { |
| .setup = setup, |
| .stats = {}, |
| .pendingReqTimeoutCount = 0, |
| }; |
| |
| // Overlength domains are dropped. |
| setup.domains = {validDomain1, domainTooLong, validDomain2}; |
| expect.setup.domains = {validDomain1, validDomain2}; |
| EXPECT_EQ(0, cacheSetupResolver(TEST_NETID, setup)); |
| EXPECT_TRUE(resolv_has_nameservers(TEST_NETID)); |
| expectCacheStats("ResolverSetup_Domains drop overlength", TEST_NETID, expect); |
| |
| // Duplicate domains are dropped. |
| setup.domains = {validDomain1, validDomain2, validDomain1, validDomain2}; |
| expect.setup.domains = {validDomain1, validDomain2}; |
| EXPECT_EQ(0, cacheSetupResolver(TEST_NETID, setup)); |
| EXPECT_TRUE(resolv_has_nameservers(TEST_NETID)); |
| expectCacheStats("ResolverSetup_Domains drop duplicates", TEST_NETID, expect); |
| } |
| |
| TEST_F(ResolvCacheTest, ResolverSetup_Prune) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| const std::vector<std::string> servers = {"127.0.0.1", "::127.0.0.2", "fe80::1", "fe80::2", |
| "fe80::3"}; |
| const std::vector<std::string> domains = {"d1.com", "d2.com", "d3.com", "d4.com", |
| "d5.com", "d6.com", "d7.com"}; |
| const SetupParams setup = { |
| .servers = servers, |
| .domains = domains, |
| .params = kParams, |
| }; |
| |
| EXPECT_EQ(0, cacheSetupResolver(TEST_NETID, setup)); |
| EXPECT_TRUE(resolv_has_nameservers(TEST_NETID)); |
| |
| const CacheStats cacheStats = { |
| .setup = {.servers = std::vector(servers.begin(), servers.begin() + MAXNS), |
| .domains = std::vector(domains.begin(), domains.begin() + MAXDNSRCH), |
| .params = setup.params}, |
| .stats = {}, |
| .pendingReqTimeoutCount = 0, |
| }; |
| expectCacheStats("ResolverSetup_Prune", TEST_NETID, cacheStats); |
| } |
| |
| TEST_F(ResolvCacheTest, GetStats) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| const SetupParams setup = { |
| .servers = {"127.0.0.1", "::127.0.0.2", "fe80::3"}, |
| .domains = {"domain1.com", "domain2.com"}, |
| .params = kParams, |
| }; |
| |
| EXPECT_EQ(0, cacheSetupResolver(TEST_NETID, setup)); |
| EXPECT_TRUE(resolv_has_nameservers(TEST_NETID)); |
| |
| const CacheStats cacheStats = { |
| .setup = setup, |
| .stats = {}, |
| .pendingReqTimeoutCount = 0, |
| }; |
| expectCacheStats("GetStats", TEST_NETID, cacheStats); |
| } |
| |
| TEST_F(ResolvCacheTest, FlushCache) { |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| const SetupParams setup = { |
| .servers = {"127.0.0.1", "::127.0.0.2", "fe80::3"}, |
| .domains = {"domain1.com", "domain2.com"}, |
| .params = kParams, |
| }; |
| EXPECT_EQ(0, cacheSetupResolver(TEST_NETID, setup)); |
| EXPECT_TRUE(resolv_has_nameservers(TEST_NETID)); |
| |
| res_sample sample = {.at = time(NULL), .rtt = 100, .rcode = ns_r_noerror}; |
| sockaddr_in sin = {.sin_family = AF_INET, .sin_port = htons(DNS_PORT)}; |
| ASSERT_TRUE(inet_pton(AF_INET, setup.servers[0].c_str(), &sin.sin_addr)); |
| cacheAddStats(TEST_NETID, 1 /*revision_id*/, IPSockAddr(sin), sample, setup.params.max_samples); |
| |
| const CacheStats cacheStats = { |
| .setup = setup, |
| .stats = {{{sample}, 1 /*sample_count*/, 1 /*sample_next*/}}, |
| .pendingReqTimeoutCount = 0, |
| }; |
| expectCacheStats("FlushCache: a record in cache stats", TEST_NETID, cacheStats); |
| |
| EXPECT_EQ(0, cacheFlush(TEST_NETID)); |
| const CacheStats cacheStats_empty = { |
| .setup = setup, |
| .stats = {}, |
| .pendingReqTimeoutCount = 0, |
| }; |
| expectCacheStats("FlushCache: no record in cache stats", TEST_NETID, cacheStats_empty); |
| } |
| |
| TEST_F(ResolvCacheTest, GetHostByAddrFromCache_InvalidArgs) { |
| char domain_name[NS_MAXDNAME] = {}; |
| const char query_v4[] = "1.2.3.5"; |
| |
| // invalid buffer size |
| EXPECT_FALSE(resolv_gethostbyaddr_from_cache(TEST_NETID, domain_name, NS_MAXDNAME + 1, nullptr, |
| AF_INET)); |
| EXPECT_STREQ("", domain_name); |
| |
| // invalid query |
| EXPECT_FALSE(resolv_gethostbyaddr_from_cache(TEST_NETID, domain_name, NS_MAXDNAME, nullptr, |
| AF_INET)); |
| EXPECT_STREQ("", domain_name); |
| |
| // unsupported AF |
| EXPECT_FALSE(resolv_gethostbyaddr_from_cache(TEST_NETID, domain_name, NS_MAXDNAME, query_v4, |
| AF_UNSPEC)); |
| EXPECT_STREQ("", domain_name); |
| } |
| |
| TEST_F(ResolvCacheTest, GetHostByAddrFromCache) { |
| char domain_name[NS_MAXDNAME] = {}; |
| const char query_v4[] = "1.2.3.5"; |
| const char query_v6[] = "2001:db8::102:304"; |
| const char query_v6_unabbreviated[] = "2001:0db8:0000:0000:0000:0000:0102:0304"; |
| const char query_v6_mixed[] = "2001:db8::1.2.3.4"; |
| const char answer[] = "existent.in.cache"; |
| |
| // cache does not exist |
| EXPECT_FALSE(resolv_gethostbyaddr_from_cache(TEST_NETID, domain_name, NS_MAXDNAME, query_v4, |
| AF_INET)); |
| EXPECT_STREQ("", domain_name); |
| |
| // cache is empty |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| EXPECT_FALSE(resolv_gethostbyaddr_from_cache(TEST_NETID, domain_name, NS_MAXDNAME, query_v4, |
| AF_INET)); |
| EXPECT_STREQ("", domain_name); |
| |
| // no v4 match in cache |
| CacheEntry ce = makeCacheEntry(QUERY, "any.data", ns_c_in, ns_t_a, "1.2.3.4"); |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce)); |
| EXPECT_FALSE(resolv_gethostbyaddr_from_cache(TEST_NETID, domain_name, NS_MAXDNAME, query_v4, |
| AF_INET)); |
| EXPECT_STREQ("", domain_name); |
| |
| // v4 match |
| ce = makeCacheEntry(QUERY, answer, ns_c_in, ns_t_a, query_v4); |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce)); |
| EXPECT_TRUE(resolv_gethostbyaddr_from_cache(TEST_NETID, domain_name, NS_MAXDNAME, query_v4, |
| AF_INET)); |
| EXPECT_STREQ(answer, domain_name); |
| |
| // no v6 match in cache |
| memset(domain_name, 0, NS_MAXDNAME); |
| EXPECT_FALSE(resolv_gethostbyaddr_from_cache(TEST_NETID, domain_name, NS_MAXDNAME, query_v6, |
| AF_INET6)); |
| EXPECT_STREQ("", domain_name); |
| |
| // v6 match |
| ce = makeCacheEntry(QUERY, answer, ns_c_in, ns_t_aaaa, query_v6); |
| EXPECT_EQ(0, cacheAdd(TEST_NETID, ce)); |
| EXPECT_TRUE(resolv_gethostbyaddr_from_cache(TEST_NETID, domain_name, NS_MAXDNAME, query_v6, |
| AF_INET6)); |
| EXPECT_STREQ(answer, domain_name); |
| |
| // v6 match with unabbreviated address format |
| memset(domain_name, 0, NS_MAXDNAME); |
| EXPECT_TRUE(resolv_gethostbyaddr_from_cache(TEST_NETID, domain_name, NS_MAXDNAME, |
| query_v6_unabbreviated, AF_INET6)); |
| EXPECT_STREQ(answer, domain_name); |
| |
| // v6 with mixed address format |
| memset(domain_name, 0, NS_MAXDNAME); |
| EXPECT_TRUE(resolv_gethostbyaddr_from_cache(TEST_NETID, domain_name, NS_MAXDNAME, |
| query_v6_mixed, AF_INET6)); |
| EXPECT_STREQ(answer, domain_name); |
| } |
| |
| TEST_F(ResolvCacheTest, GetResolverStats) { |
| const res_sample sample1 = {.at = time(nullptr), .rtt = 100, .rcode = ns_r_noerror}; |
| const res_sample sample2 = {.at = time(nullptr), .rtt = 200, .rcode = ns_r_noerror}; |
| const res_sample sample3 = {.at = time(nullptr), .rtt = 300, .rcode = ns_r_noerror}; |
| const res_stats expectedStats[MAXNS] = { |
| {{sample1}, 1 /*sample_count*/, 1 /*sample_next*/}, |
| {{sample2}, 1, 1}, |
| {{sample3}, 1, 1}, |
| }; |
| std::vector<IPSockAddr> nameserverSockAddrs = { |
| IPSockAddr::toIPSockAddr("127.0.0.1", DNS_PORT), |
| IPSockAddr::toIPSockAddr("::127.0.0.2", DNS_PORT), |
| IPSockAddr::toIPSockAddr("fe80::3", DNS_PORT), |
| }; |
| const SetupParams setup = { |
| .servers = {"127.0.0.1", "::127.0.0.2", "fe80::3"}, |
| .domains = {"domain1.com", "domain2.com"}, |
| .params = kParams, |
| }; |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| EXPECT_EQ(0, cacheSetupResolver(TEST_NETID, setup)); |
| int revision_id = 1; |
| cacheAddStats(TEST_NETID, revision_id, nameserverSockAddrs[0], sample1, |
| setup.params.max_samples); |
| cacheAddStats(TEST_NETID, revision_id, nameserverSockAddrs[1], sample2, |
| setup.params.max_samples); |
| cacheAddStats(TEST_NETID, revision_id, nameserverSockAddrs[2], sample3, |
| setup.params.max_samples); |
| |
| res_stats cacheStats[MAXNS]{}; |
| res_params params; |
| EXPECT_EQ(resolv_cache_get_resolver_stats(TEST_NETID, ¶ms, cacheStats, nameserverSockAddrs), |
| revision_id); |
| EXPECT_TRUE(params == kParams); |
| for (size_t i = 0; i < MAXNS; i++) { |
| EXPECT_TRUE(cacheStats[i] == expectedStats[i]); |
| } |
| |
| // pass another list of IPSockAddr |
| const res_stats expectedStats2[MAXNS] = { |
| {{sample3, sample2}, 2, 2}, |
| {{sample2}, 1, 1}, |
| {{sample1}, 1, 1}, |
| }; |
| nameserverSockAddrs = { |
| IPSockAddr::toIPSockAddr("fe80::3", DNS_PORT), |
| IPSockAddr::toIPSockAddr("::127.0.0.2", DNS_PORT), |
| IPSockAddr::toIPSockAddr("127.0.0.1", DNS_PORT), |
| }; |
| cacheAddStats(TEST_NETID, revision_id, nameserverSockAddrs[0], sample2, |
| setup.params.max_samples); |
| EXPECT_EQ(resolv_cache_get_resolver_stats(TEST_NETID, ¶ms, cacheStats, nameserverSockAddrs), |
| revision_id); |
| EXPECT_TRUE(params == kParams); |
| for (size_t i = 0; i < MAXNS; i++) { |
| EXPECT_TRUE(cacheStats[i] == expectedStats2[i]); |
| } |
| } |
| |
| TEST_F(ResolvCacheTest, IsEnforceDnsUidEnabled) { |
| const SetupParams unenforcedDnsUidCfg = { |
| .servers = {"127.0.0.1", "::127.0.0.2", "fe80::3"}, |
| .domains = {"domain1.com", "domain2.com"}, |
| .params = kParams, |
| }; |
| // Network #1 |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| EXPECT_EQ(0, cacheSetupResolver(TEST_NETID, unenforcedDnsUidCfg)); |
| EXPECT_FALSE(resolv_is_enforceDnsUid_enabled_network(TEST_NETID)); |
| |
| // Network #2 |
| EXPECT_EQ(0, cacheCreate(TEST_NETID + 1)); |
| EXPECT_EQ(0, cacheSetupResolver(TEST_NETID + 1, unenforcedDnsUidCfg)); |
| EXPECT_FALSE(resolv_is_enforceDnsUid_enabled_network(TEST_NETID + 1)); |
| |
| // Change the enforceDnsUid setting on network #1 |
| const SetupParams enforcedDnsUidCfg = { |
| .servers = {"127.0.0.1", "::127.0.0.2", "fe80::3"}, |
| .domains = {"domain1.com", "domain2.com"}, |
| .params = kParams, |
| .resolverOptions = {.enforceDnsUid = true}, |
| }; |
| EXPECT_EQ(0, cacheSetupResolver(TEST_NETID, enforcedDnsUidCfg)); |
| EXPECT_TRUE(resolv_is_enforceDnsUid_enabled_network(TEST_NETID)); |
| |
| // Network #2 is unaffected |
| EXPECT_FALSE(resolv_is_enforceDnsUid_enabled_network(TEST_NETID + 1)); |
| |
| // Returns false on non-existent network |
| EXPECT_FALSE(resolv_is_enforceDnsUid_enabled_network(TEST_NETID + 2)); |
| } |
| |
| TEST_F(ResolvCacheTest, IsNetworkMetered) { |
| const SetupParams defaultCfg = { |
| .servers = {"127.0.0.1"}, |
| .domains = {"domain1.com"}, |
| .params = kParams, |
| }; |
| // Network #1 |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| EXPECT_EQ(0, cacheSetupResolver(TEST_NETID, defaultCfg)); |
| EXPECT_FALSE(resolv_is_metered_network(TEST_NETID)); |
| |
| // Network #2 |
| EXPECT_EQ(0, cacheCreate(TEST_NETID + 1)); |
| EXPECT_EQ(0, cacheSetupResolver(TEST_NETID + 1, defaultCfg)); |
| EXPECT_FALSE(resolv_is_metered_network(TEST_NETID + 1)); |
| |
| // Change the metered setting on network #1 |
| const SetupParams meteredCfg = { |
| .servers = {"127.0.0.1"}, |
| .domains = {"domain1.com"}, |
| .params = kParams, |
| .metered = true, |
| }; |
| EXPECT_EQ(0, cacheSetupResolver(TEST_NETID, meteredCfg)); |
| EXPECT_TRUE(resolv_is_metered_network(TEST_NETID)); |
| |
| // Network #2 is unaffected |
| EXPECT_FALSE(resolv_is_metered_network(TEST_NETID + 1)); |
| |
| // Returns false on non-existent network |
| EXPECT_FALSE(resolv_is_metered_network(TEST_NETID + 2)); |
| } |
| |
| TEST_F(ResolvCacheTest, setAllowBypassingPrivateDnsOnNetwork) { |
| // Create the cache for the test network. |
| EXPECT_EQ(0, cacheCreate(TEST_NETID)); |
| EXPECT_TRUE(has_named_cache(TEST_NETID)); |
| |
| // Allow bypassing the private DNS rule for a UID on a nonexistent network. |
| EXPECT_EQ(-ENOENT, |
| setAllowBypassPrivateDnsOnNetwork(TEST_NETID_2, TEST_UID, true /* allowed */)); |
| EXPECT_FALSE(isUidAllowedBypassPrivateDnsOnNetwork(TEST_NETID_2, TEST_UID)); |
| |
| // Allow bypassing the private DNS rule for a UID on a created network. |
| EXPECT_EQ(0, setAllowBypassPrivateDnsOnNetwork(TEST_NETID, TEST_UID, true /* allowed */)); |
| EXPECT_TRUE(isUidAllowedBypassPrivateDnsOnNetwork(TEST_NETID, TEST_UID)); |
| |
| // Allow bypassing the private DNS rule for a UID on a created network |
| // again. |
| EXPECT_EQ(-EEXIST, setAllowBypassPrivateDnsOnNetwork(TEST_NETID, TEST_UID, true /* allowed */)); |
| EXPECT_TRUE(isUidAllowedBypassPrivateDnsOnNetwork(TEST_NETID, TEST_UID)); |
| |
| // Disallow bypassing the private DNS rule for a UID on a created network. |
| EXPECT_EQ(0, setAllowBypassPrivateDnsOnNetwork(TEST_NETID, TEST_UID, false /* allowed */)); |
| EXPECT_FALSE(isUidAllowedBypassPrivateDnsOnNetwork(TEST_NETID, TEST_UID)); |
| |
| // Disallow bypassing the private DNS rule for a UID on a created network |
| // again. |
| EXPECT_EQ(-ENOENT, |
| setAllowBypassPrivateDnsOnNetwork(TEST_NETID, TEST_UID, false /* allowed */)); |
| EXPECT_FALSE(isUidAllowedBypassPrivateDnsOnNetwork(TEST_NETID, TEST_UID)); |
| } |
| |
| namespace { |
| |
| constexpr int EAI_OK = 0; |
| constexpr char DNS_EVENT_SUBSAMPLING_MAP_FLAG[] = |
| "persist.device_config.netd_native.dns_event_subsample_map"; |
| constexpr char MDNS_EVENT_SUBSAMPLING_MAP_FLAG[] = |
| "persist.device_config.netd_native.mdns_event_subsample_map"; |
| |
| class ScopedCacheCreate { |
| public: |
| explicit ScopedCacheCreate(unsigned netid, const char* subsampling_map, const char* property) |
| : mStoredNetId(netid), mStoredProperty(property) { |
| property_get(property, mStoredMap, ""); |
| property_set(property, subsampling_map); |
| EXPECT_EQ(0, resolv_create_cache_for_net(netid)); |
| } |
| ~ScopedCacheCreate() { |
| resolv_delete_cache_for_net(mStoredNetId); |
| property_set(mStoredProperty, mStoredMap); |
| } |
| |
| private: |
| unsigned mStoredNetId; |
| const char* mStoredProperty; |
| char mStoredMap[PROPERTY_VALUE_MAX]{}; |
| }; |
| |
| } // namespace |
| |
| TEST_F(ResolvCacheTest, DnsEventSubsampling) { |
| // Test defaults, default flag is "default:8 0:400 2:110 7:110" if no experiment flag is set |
| { |
| ScopedCacheCreate scopedCacheCreate(TEST_NETID, "", DNS_EVENT_SUBSAMPLING_MAP_FLAG); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_AGAIN, false), 110U); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_NODATA, false), 110U); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_OK, false), 400U); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_BADFLAGS, false), |
| 8U); // default |
| EXPECT_THAT(resolv_cache_dump_subsampling_map(TEST_NETID, false), |
| testing::UnorderedElementsAreArray({"default:8", "0:400", "2:110", "7:110"})); |
| } |
| // Now change the experiment flag to "0:42 default:666" |
| { |
| ScopedCacheCreate scopedCacheCreate(TEST_NETID, "0:42 default:666", |
| DNS_EVENT_SUBSAMPLING_MAP_FLAG); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_OK, false), 42U); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_NODATA, false), |
| 666U); // default |
| EXPECT_THAT(resolv_cache_dump_subsampling_map(TEST_NETID, false), |
| testing::UnorderedElementsAreArray({"default:666", "0:42"})); |
| } |
| // Now change the experiment flag to something illegal |
| { |
| ScopedCacheCreate scopedCacheCreate(TEST_NETID, "asvaxx", DNS_EVENT_SUBSAMPLING_MAP_FLAG); |
| // 0(disable log) is the default value if experiment flag is invalid. |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_OK, false), 0U); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_NODATA, false), 0U); |
| EXPECT_TRUE(resolv_cache_dump_subsampling_map(TEST_NETID, false).empty()); |
| } |
| // Test negative and zero denom |
| { |
| ScopedCacheCreate scopedCacheCreate(TEST_NETID, "0:-42 default:-666 7:10 10:0", |
| DNS_EVENT_SUBSAMPLING_MAP_FLAG); |
| // 0(disable log) is the default value if no valid denom is set |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_OK, false), 0U); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_BADFLAGS, false), 0U); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_NODATA, false), 10U); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_SOCKTYPE, false), 0U); |
| EXPECT_THAT(resolv_cache_dump_subsampling_map(TEST_NETID, false), |
| testing::UnorderedElementsAreArray({"7:10", "10:0"})); |
| } |
| } |
| |
| TEST_F(ResolvCacheTest, MdnsEventSubsampling) { |
| // Test defaults, DEFAULT_MDNS_SUBSAMPLING_MAP is "default:1" if no experiment flag is set |
| { |
| ScopedCacheCreate scopedCacheCreate(TEST_NETID, "", MDNS_EVENT_SUBSAMPLING_MAP_FLAG); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_AGAIN, true), |
| 1U); // default for all return_code |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_NODATA, true), 1U); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_BADFLAGS, true), 1U); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_OK, true), 1U); |
| // not equal to DEFAULT_SUBSAMPLING_MAP[] = "default:8 0:400 2:110 7:110"; |
| EXPECT_NE(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_AGAIN, true), 110U); |
| EXPECT_NE(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_NODATA, true), 110U); |
| EXPECT_NE(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_OK, true), 400U); |
| EXPECT_NE(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_BADFLAGS, true), 8U); |
| EXPECT_THAT(resolv_cache_dump_subsampling_map(TEST_NETID, true), |
| testing::UnorderedElementsAreArray({"default:1"})); |
| } |
| // Now change the experiment flag to "default:1 0:10" |
| { |
| ScopedCacheCreate scopedCacheCreate(TEST_NETID, "0:10 default:1", |
| MDNS_EVENT_SUBSAMPLING_MAP_FLAG); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_OK, true), 10U); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_NODATA, true), 1U); // default |
| EXPECT_THAT(resolv_cache_dump_subsampling_map(TEST_NETID, true), |
| testing::UnorderedElementsAreArray({"0:10", "default:1"})); |
| } |
| // Now change the experiment flag to something illegal |
| { |
| ScopedCacheCreate scopedCacheCreate(TEST_NETID, "asvaxx", MDNS_EVENT_SUBSAMPLING_MAP_FLAG); |
| // 0(disable log) is the default value if experiment flag is invalid. |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_OK, true), 0U); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_NODATA, true), 0U); |
| EXPECT_TRUE(resolv_cache_dump_subsampling_map(TEST_NETID, true).empty()); |
| } |
| // Test negative and zero denom |
| { |
| ScopedCacheCreate scopedCacheCreate(TEST_NETID, "0:-42 default:-666 7:10 10:0", |
| MDNS_EVENT_SUBSAMPLING_MAP_FLAG); |
| // 0(disable log) is the default value if no valid denom is set |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_OK, true), 0U); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_BADFLAGS, true), 0U); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_NODATA, true), 10U); |
| EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_SOCKTYPE, true), 0U); |
| EXPECT_THAT(resolv_cache_dump_subsampling_map(TEST_NETID, true), |
| testing::UnorderedElementsAreArray({"7:10", "10:0"})); |
| } |
| } |
| // TODO: Tests for NetConfig, including: |
| // - res_stats |
| // -- _resolv_cache_add_resolver_stats_sample() |
| // -- android_net_res_stats_get_info_for_net() |
| // TODO: inject a mock timer into the cache to make TTL tests pass instantly |
| // TODO: test TTL of RFC 2308 negative caching |