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;