Customized hostname/address table for DNS query
- Some Android OEMs would like to create their own customized table
(hostname/address mapping lists) that the resolver looks up before
querying dns servers and gets the result directly.
- About DNS query, resolver checks system/etc/hosts first. If no data is
found, resolver will query dns from the server. We add a new parameter to
ResolverParamsParcel for OEMs to customize the table and OEMs can use
setDnsConfigurationForNetwork to create the hostname/address lists. After
OEMs create their own lists,the dns query will be following sequence.
system/ect/hosts -> customized table -> dns server
- Add test cases: GetAddrInfoFromCustTable,
GetAddrInfoFromCustTable_InvalidInput,
GetAddrInfoFromCustTable_Modify
Bug: 122998288
Test: cd packages/modules/DnsResolver && atest
Change-Id: I7a2d689de74f0076f0115e0cb20fdc028903ae86
diff --git a/Android.bp b/Android.bp
index 28cb03e..4495d4e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -18,6 +18,7 @@
local_include_dir: "binder",
srcs: [
"binder/android/net/IDnsResolver.aidl",
+ "binder/android/net/ResolverHostsParcel.aidl",
"binder/android/net/ResolverParamsParcel.aidl",
],
imports: [
@@ -185,8 +186,7 @@
"libbinder_ndk",
],
static_libs: [
- "dnsresolver_aidl_interface-cpp",
- "dnsresolver_aidl_interface-ndk_platform",
+ "dnsresolver_aidl_interface-unstable-ndk_platform",
"netd_event_listener_interface-ndk_platform",
"libgmock",
"liblog",
diff --git a/ResolverController.cpp b/ResolverController.cpp
index df3f457..f25a8d9 100644
--- a/ResolverController.cpp
+++ b/ResolverController.cpp
@@ -232,7 +232,7 @@
res_params.retry_count = resolverParams.retryCount;
return resolv_set_nameservers(resolverParams.netId, resolverParams.servers,
- resolverParams.domains, res_params);
+ resolverParams.domains, res_params, resolverParams.hosts);
}
int ResolverController::getResolverInfo(int32_t netId, std::vector<std::string>* servers,
diff --git a/binder/android/net/ResolverHostsParcel.aidl b/binder/android/net/ResolverHostsParcel.aidl
new file mode 100644
index 0000000..6b372b1
--- /dev/null
+++ b/binder/android/net/ResolverHostsParcel.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+package android.net;
+
+/**
+ * A mapping list which translates hostnames or domain names to IP addresses
+ * locally. Mapping multiple addresses to one hostname is supported.
+ * It's similar to /etc/hosts file.
+ *
+ * {@hide}
+ */
+parcelable ResolverHostsParcel {
+ /**
+ * The IPv4 or IPv6 address corresponding to |hostName| field.
+ */
+ @utf8InCpp String ipAddr;
+
+ /**
+ * The hostname is a FQDN.
+ */
+ @utf8InCpp String hostName = "";
+}
diff --git a/binder/android/net/ResolverParamsParcel.aidl b/binder/android/net/ResolverParamsParcel.aidl
index 76d9612..a4011fb 100644
--- a/binder/android/net/ResolverParamsParcel.aidl
+++ b/binder/android/net/ResolverParamsParcel.aidl
@@ -16,6 +16,8 @@
package android.net;
+import android.net.ResolverHostsParcel;
+
/**
* Configuration for a resolver parameters.
*
@@ -97,4 +99,15 @@
* by the experiement flag.
*/
int tlsConnectTimeoutMs = 0;
+
+ /**
+ * An IP/hostname mapping table for DNS local lookup customization.
+ * WARNING: this is intended for local testing and other special situations.
+ * Future versions of the DnsResolver module may break your assumptions.
+ * Injecting static mappings for public hostnames is generally A VERY BAD IDEA,
+ * since it makes it impossible for the domain owners to migrate the domain.
+ * It is also not an effective domain blocking mechanism, because apps can
+ * easily hardcode IPs or bypass the system DNS resolver.
+ */
+ ResolverHostsParcel[] hosts = {};
}
diff --git a/getaddrinfo.cpp b/getaddrinfo.cpp
index 6e2376b..d5ec682 100644
--- a/getaddrinfo.cpp
+++ b/getaddrinfo.cpp
@@ -140,7 +140,9 @@
static void _sethtent(FILE**);
static void _endhtent(FILE**);
static struct addrinfo* _gethtent(FILE**, const char*, const struct addrinfo*);
-static bool files_getaddrinfo(const char* name, const addrinfo* pai, addrinfo** res);
+static struct addrinfo* getCustomHosts(const size_t netid, const char*, const struct addrinfo*);
+static bool files_getaddrinfo(const size_t netid, const char* name, const addrinfo* pai,
+ addrinfo** res);
static int _find_src_addr(const struct sockaddr*, struct sockaddr*, unsigned, uid_t);
static int res_queryN(const char* name, res_target* target, res_state res, int* herrno);
@@ -464,7 +466,7 @@
// If the servname does not match socktype/protocol, return error code.
if ((error = get_portmatch(pai, servname))) return error;
- if (!files_getaddrinfo(hostname, pai, &result)) {
+ if (!files_getaddrinfo(netcontext->dns_netid, hostname, pai, &result)) {
error = dns_getaddrinfo(hostname, pai, netcontext, &result, event);
}
if (error) {
@@ -1527,22 +1529,43 @@
return res0;
}
-static bool files_getaddrinfo(const char* name, const addrinfo* pai, addrinfo** res) {
+static struct addrinfo* getCustomHosts(const size_t netid, const char* _Nonnull name,
+ const struct addrinfo* _Nonnull pai) {
+ struct addrinfo sentinel = {};
+ struct addrinfo *res0, *res;
+ res = &sentinel;
+ std::vector<std::string> hosts = getCustomizedTableByName(netid, name);
+ for (const std::string& host : hosts) {
+ int error = getaddrinfo_numeric(host.c_str(), nullptr, *pai, &res0);
+ if (!error && res0 != nullptr) {
+ res->ai_next = res0;
+ res = res0;
+ res0 = nullptr;
+ }
+ }
+ return sentinel.ai_next;
+}
+
+static bool files_getaddrinfo(const size_t netid, const char* name, const addrinfo* pai,
+ addrinfo** res) {
struct addrinfo sentinel = {};
struct addrinfo *p, *cur;
- FILE* hostf = NULL;
+ FILE* hostf = nullptr;
cur = &sentinel;
-
_sethtent(&hostf);
- while ((p = _gethtent(&hostf, name, pai)) != NULL) {
+ while ((p = _gethtent(&hostf, name, pai)) != nullptr) {
cur->ai_next = p;
while (cur && cur->ai_next) cur = cur->ai_next;
}
_endhtent(&hostf);
+ if ((p = getCustomHosts(netid, name, pai)) != nullptr) {
+ cur->ai_next = p;
+ }
+
*res = sentinel.ai_next;
- return sentinel.ai_next != NULL;
+ return sentinel.ai_next != nullptr;
}
/* resolver logic */
diff --git a/res_cache.cpp b/res_cache.cpp
index 4ed893e..110d19b 100644
--- a/res_cache.cpp
+++ b/res_cache.cpp
@@ -995,6 +995,9 @@
// Map format: ReturnCode:rate_denom
std::unordered_map<int, uint32_t> dns_event_subsampling_map;
DnsStats dnsStats;
+ // Customized hostname/address table will be stored in customizedTable.
+ // If resolverParams.hosts is empty, the existing customized table will be erased.
+ HostMapping customizedTable = {};
};
/* gets cache associated with a network, or NULL if none exists */
@@ -1548,8 +1551,24 @@
} // namespace
-int resolv_set_nameservers(unsigned netid, const std::vector<std::string>& servers,
- const std::vector<std::string>& domains, const res_params& params) {
+std::vector<std::string> getCustomizedTableByName(const size_t netid, const char* hostname) {
+ std::lock_guard guard(cache_mutex);
+ NetConfig* netconfig = find_netconfig_locked(netid);
+
+ std::vector<std::string> result;
+ if (netconfig != nullptr) {
+ const auto& hosts = netconfig->customizedTable.equal_range(hostname);
+ for (auto i = hosts.first; i != hosts.second; ++i) {
+ result.push_back(i->second);
+ }
+ }
+ return result;
+}
+
+int resolv_set_nameservers(
+ unsigned netid, const std::vector<std::string>& servers,
+ const std::vector<std::string>& domains, const res_params& params,
+ const std::vector<::aidl::android::net::ResolverHostsParcel>& customizedTable) {
std::vector<std::string> nameservers = filter_nameservers(servers);
const int numservers = static_cast<int>(nameservers.size());
@@ -1603,6 +1622,11 @@
LOG(WARNING) << __func__ << ": netid = " << netid << ", failed to set dns stats";
return -EINVAL;
}
+ netconfig->customizedTable.clear();
+ for (const auto& host : customizedTable) {
+ if (!host.hostName.empty() && !host.ipAddr.empty())
+ netconfig->customizedTable.emplace(host.hostName, host.ipAddr);
+ }
return 0;
}
diff --git a/resolv_cache.h b/resolv_cache.h
index c838345..020bbd2 100644
--- a/resolv_cache.h
+++ b/resolv_cache.h
@@ -31,6 +31,7 @@
#include <unordered_map>
#include <vector>
+#include <aidl/android/net/ResolverHostsParcel.h>
#include <netdutils/DumpWriter.h>
#include <netdutils/InternetAddresses.h>
#include <stats.pb.h>
@@ -42,6 +43,9 @@
// The name servers are retrieved from the cache which is associated
// with the network to which ResState is associated.
struct ResState;
+
+typedef std::multimap<std::string /* hostname */, std::string /* IPv4/IPv6 address */> HostMapping;
+
void resolv_populate_res_for_net(ResState* statp);
std::vector<unsigned> resolv_list_caches();
@@ -68,9 +72,14 @@
/* Notify the cache a request failed */
void _resolv_cache_query_failed(unsigned netid, const void* query, int querylen, uint32_t flags);
+// Get a customized table for a given network.
+std::vector<std::string> getCustomizedTableByName(const size_t netid, const char* hostname);
+
// Sets name servers for a given network.
-int resolv_set_nameservers(unsigned netid, const std::vector<std::string>& servers,
- const std::vector<std::string>& domains, const res_params& params);
+int resolv_set_nameservers(
+ unsigned netid, const std::vector<std::string>& servers,
+ const std::vector<std::string>& domains, const res_params& params,
+ const std::vector<::aidl::android::net::ResolverHostsParcel>& customizedTable = {});
// Creates the cache associated with the given network.
int resolv_create_cache_for_net(unsigned netid);
diff --git a/tests/resolv_integration_test.cpp b/tests/resolv_integration_test.cpp
index 4f8f976..687103d 100644
--- a/tests/resolv_integration_test.cpp
+++ b/tests/resolv_integration_test.cpp
@@ -1000,6 +1000,143 @@
EXPECT_EQ(dns2.queries().size(), 5U);
}
+TEST_F(ResolverTest, GetAddrInfoFromCustTable_InvalidInput) {
+ constexpr char hostnameNoip[] = "noip.example.com.";
+ constexpr char hostnameInvalidip[] = "invalidip.example.com.";
+ const std::vector<aidl::android::net::ResolverHostsParcel> invalidCustHosts = {
+ {"", hostnameNoip},
+ {"wrong IP", hostnameInvalidip},
+ };
+ test::DNSResponder dns;
+ StartDns(dns, {});
+ auto resolverParams = DnsResponderClient::GetDefaultResolverParamsParcel();
+ resolverParams.hosts = invalidCustHosts;
+ ASSERT_TRUE(mDnsClient.resolvService()->setResolverConfiguration(resolverParams).isOk());
+ for (const auto& hostname : {hostnameNoip, hostnameInvalidip}) {
+ // The query won't get data from customized table because of invalid customized table
+ // and DNSResponder also has no records. hostnameNoip has never registered and
+ // hostnameInvalidip has registered but wrong IP.
+ const addrinfo hints = {.ai_family = AF_UNSPEC};
+ ScopedAddrinfo result = safe_getaddrinfo(hostname, nullptr, &hints);
+ ASSERT_TRUE(result == nullptr);
+ EXPECT_EQ(4U, GetNumQueries(dns, hostname));
+ }
+}
+
+TEST_F(ResolverTest, GetAddrInfoFromCustTable) {
+ constexpr char hostnameV4[] = "v4only.example.com.";
+ constexpr char hostnameV6[] = "v6only.example.com.";
+ constexpr char hostnameV4V6[] = "v4v6.example.com.";
+ constexpr char custAddrV4[] = "1.2.3.4";
+ constexpr char custAddrV6[] = "::1.2.3.4";
+ constexpr char dnsSvAddrV4[] = "1.2.3.5";
+ constexpr char dnsSvAddrV6[] = "::1.2.3.5";
+ const std::vector<aidl::android::net::ResolverHostsParcel> custHostV4 = {
+ {custAddrV4, hostnameV4},
+ };
+ const std::vector<aidl::android::net::ResolverHostsParcel> custHostV6 = {
+ {custAddrV6, hostnameV6},
+ };
+ const std::vector<aidl::android::net::ResolverHostsParcel> custHostV4V6 = {
+ {custAddrV4, hostnameV4V6},
+ {custAddrV6, hostnameV4V6},
+ };
+ const std::vector<DnsRecord> dnsSvHostV4 = {
+ {hostnameV4, ns_type::ns_t_a, dnsSvAddrV4},
+ };
+ const std::vector<DnsRecord> dnsSvHostV6 = {
+ {hostnameV6, ns_type::ns_t_aaaa, dnsSvAddrV6},
+ };
+ const std::vector<DnsRecord> dnsSvHostV4V6 = {
+ {hostnameV4V6, ns_type::ns_t_a, dnsSvAddrV4},
+ {hostnameV4V6, ns_type::ns_t_aaaa, dnsSvAddrV6},
+ };
+ struct TestConfig {
+ const std::string name;
+ const std::vector<aidl::android::net::ResolverHostsParcel> customizedHosts;
+ const std::vector<DnsRecord> dnsserverHosts;
+ const std::vector<std::string> queryResult;
+ std::string asParameters() const {
+ return StringPrintf("name: %s, customizedHosts: %s, dnsserverHosts: %s", name.c_str(),
+ customizedHosts.empty() ? "No" : "Yes",
+ dnsserverHosts.empty() ? "No" : "Yes");
+ }
+ } testConfigs[]{
+ // clang-format off
+ {hostnameV4, {}, {}, {}},
+ {hostnameV4, {}, dnsSvHostV4, {dnsSvAddrV4}},
+ {hostnameV4, custHostV4, {}, {custAddrV4}},
+ {hostnameV4, custHostV4, dnsSvHostV4, {custAddrV4}},
+ {hostnameV6, {}, {}, {}},
+ {hostnameV6, {}, dnsSvHostV6, {dnsSvAddrV6}},
+ {hostnameV6, custHostV6, {}, {custAddrV6}},
+ {hostnameV6, custHostV6, dnsSvHostV6, {custAddrV6}},
+ {hostnameV4V6, {}, {}, {}},
+ {hostnameV4V6, {}, dnsSvHostV4V6, {dnsSvAddrV4, dnsSvAddrV6}},
+ {hostnameV4V6, custHostV4V6, {}, {custAddrV4, custAddrV6}},
+ {hostnameV4V6, custHostV4V6, dnsSvHostV4V6, {custAddrV4, custAddrV6}},
+ // clang-format on
+ };
+
+ for (const auto& config : testConfigs) {
+ SCOPED_TRACE(config.asParameters());
+
+ test::DNSResponder dns;
+ StartDns(dns, config.dnsserverHosts);
+
+ auto resolverParams = DnsResponderClient::GetDefaultResolverParamsParcel();
+ resolverParams.hosts = config.customizedHosts;
+ ASSERT_TRUE(mDnsClient.resolvService()->setResolverConfiguration(resolverParams).isOk());
+ const addrinfo hints = {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM};
+ ScopedAddrinfo result = safe_getaddrinfo(config.name.c_str(), nullptr, &hints);
+ if (config.customizedHosts.empty() && config.dnsserverHosts.empty()) {
+ ASSERT_TRUE(result == nullptr);
+ EXPECT_EQ(2U, GetNumQueries(dns, config.name.c_str()));
+ } else {
+ ASSERT_TRUE(result != nullptr);
+ EXPECT_THAT(ToStrings(result), testing::UnorderedElementsAreArray(config.queryResult));
+ EXPECT_EQ(config.customizedHosts.empty() ? 2U : 0U,
+ GetNumQueries(dns, config.name.c_str()));
+ }
+
+ EXPECT_TRUE(mDnsClient.resolvService()->flushNetworkCache(TEST_NETID).isOk());
+ }
+}
+
+TEST_F(ResolverTest, GetAddrInfoFromCustTable_Modify) {
+ constexpr char hostnameV4V6[] = "v4v6.example.com.";
+ constexpr char custAddrV4[] = "1.2.3.4";
+ constexpr char custAddrV6[] = "::1.2.3.4";
+ constexpr char dnsSvAddrV4[] = "1.2.3.5";
+ constexpr char dnsSvAddrV6[] = "::1.2.3.5";
+ const std::vector<DnsRecord> dnsSvHostV4V6 = {
+ {hostnameV4V6, ns_type::ns_t_a, dnsSvAddrV4},
+ {hostnameV4V6, ns_type::ns_t_aaaa, dnsSvAddrV6},
+ };
+ const std::vector<aidl::android::net::ResolverHostsParcel> custHostV4V6 = {
+ {custAddrV4, hostnameV4V6},
+ {custAddrV6, hostnameV4V6},
+ };
+ test::DNSResponder dns;
+ StartDns(dns, dnsSvHostV4V6);
+ auto resolverParams = DnsResponderClient::GetDefaultResolverParamsParcel();
+
+ resolverParams.hosts = custHostV4V6;
+ ASSERT_TRUE(mDnsClient.resolvService()->setResolverConfiguration(resolverParams).isOk());
+ const addrinfo hints = {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM};
+ ScopedAddrinfo result = safe_getaddrinfo(hostnameV4V6, nullptr, &hints);
+ ASSERT_TRUE(result != nullptr);
+ EXPECT_THAT(ToStrings(result), testing::UnorderedElementsAreArray({custAddrV4, custAddrV6}));
+ EXPECT_EQ(0U, GetNumQueries(dns, hostnameV4V6));
+
+ resolverParams.hosts = {};
+ ASSERT_TRUE(mDnsClient.resolvService()->setResolverConfiguration(resolverParams).isOk());
+ result = safe_getaddrinfo(hostnameV4V6, nullptr, &hints);
+ ASSERT_TRUE(result != nullptr);
+ EXPECT_THAT(ToStrings(result), testing::UnorderedElementsAreArray({dnsSvAddrV4, dnsSvAddrV6}));
+ EXPECT_EQ(2U, GetNumQueries(dns, hostnameV4V6));
+}
+
TEST_F(ResolverTest, EmptySetup) {
std::vector<std::string> servers;
std::vector<std::string> domains;