Implementation of control flags in asynchronous DNS query API

Flags definitions are in multinetwork.h

Test: built, flashed, booted
      system/netd/tests/runtests.sh passes

Change-Id: Iab1983b783d1470bc1cf23489abbef7a2d88e860
diff --git a/resolv/getaddrinfo.cpp b/resolv/getaddrinfo.cpp
index 860eb98..4a46c1a 100644
--- a/resolv/getaddrinfo.cpp
+++ b/resolv/getaddrinfo.cpp
@@ -1635,7 +1635,7 @@
             return n;
         }
 
-        n = res_nsend(res, buf, n, answer, anslen, &rcode);
+        n = res_nsend(res, buf, n, answer, anslen, &rcode, 0);
         if (n < 0 || hp->rcode != NOERROR || ntohs(hp->ancount) == 0) {
             // Record rcode from DNS response header only if no timeout.
             // Keep rcode timeout for reporting later if any.
diff --git a/resolv/include/netd_resolv/resolv.h b/resolv/include/netd_resolv/resolv.h
index e30f31a..4f43107 100644
--- a/resolv/include/netd_resolv/resolv.h
+++ b/resolv/include/netd_resolv/resolv.h
@@ -32,6 +32,8 @@
  * This header contains declarations related to per-network DNS server selection.
  * They are used by system/netd/ and should not be exposed by the public NDK headers.
  */
+#include <android/multinetwork.h>  // ResNsendFlags
+
 #include <netinet/in.h>
 
 #include "params.h"
@@ -113,9 +115,9 @@
 LIBNETD_RESOLV_PUBLIC bool resolv_has_nameservers(unsigned netid);
 
 // Query dns with raw msg
-// TODO: Add a way to control query parameter, like flags, or maybe res_options or even res_state.
-LIBNETD_RESOLV_PUBLIC int resolv_res_nsend(const android_net_context* netContext, const u_char* msg,
-                                           int msgLen, u_char* ans, int ansLen, int* rcode);
+LIBNETD_RESOLV_PUBLIC int resolv_res_nsend(const android_net_context* netContext,
+                                           const uint8_t* msg, int msgLen, uint8_t* ans, int ansLen,
+                                           int* rcode, uint32_t flags);
 
 // Set name servers for a network
 LIBNETD_RESOLV_PUBLIC int resolv_set_nameservers_for_net(unsigned netid, const char** servers,
diff --git a/resolv/include/netd_resolv/resolv_stub.h b/resolv/include/netd_resolv/resolv_stub.h
index fddabcf..b4364ba 100644
--- a/resolv/include/netd_resolv/resolv_stub.h
+++ b/resolv/include/netd_resolv/resolv_stub.h
@@ -66,7 +66,7 @@
     void (*resolv_register_private_dns_callback)(private_dns_validated_callback callback);
 
     int (*resolv_res_nsend)(const android_net_context* netContext, const u_char* msg, int msgLen,
-                            u_char* ans, int ansLen, int* rcode);
+                            u_char* ans, int ansLen, int* rcode, uint32_t flags);
 
     int (*resolv_set_nameservers_for_net)(unsigned netid, const char** servers, unsigned numservers,
                                           const char* domains, const __res_params* params);
diff --git a/resolv/res_cache.cpp b/resolv/res_cache.cpp
index 60a60df..e7a3b9b 100644
--- a/resolv/res_cache.cpp
+++ b/resolv/res_cache.cpp
@@ -49,7 +49,6 @@
 
 #include <android-base/logging.h>
 
-#include "netd_resolv/resolv.h"
 #include "res_state_ext.h"
 #include "resolv_cache.h"
 #include "resolv_private.h"
@@ -1248,7 +1247,11 @@
 }
 
 /* notify the cache that the query failed */
-void _resolv_cache_query_failed(unsigned netid, const void* query, int querylen) {
+void _resolv_cache_query_failed(unsigned netid, const void* query, int querylen, uint32_t flags) {
+    // We should not notify with these flags.
+    if (flags & (ANDROID_RESOLV_NO_CACHE_STORE | ANDROID_RESOLV_NO_CACHE_LOOKUP)) {
+        return;
+    }
     Entry key[1];
     Cache* cache;
 
@@ -1463,7 +1466,11 @@
 }
 
 ResolvCacheStatus _resolv_cache_lookup(unsigned netid, const void* query, int querylen,
-                                       void* answer, int answersize, int* answerlen) {
+                                       void* answer, int answersize, int* answerlen,
+                                       uint32_t flags) {
+    if (flags & ANDROID_RESOLV_NO_CACHE_LOOKUP) {
+        return RESOLV_CACHE_SKIP;
+    }
     Entry key[1];
     Entry** lookup;
     Entry* e;
@@ -1498,6 +1505,11 @@
 
     if (e == NULL) {
         VLOG << "NOT IN CACHE";
+        // If it is no-cache-store mode, we won't wait for possible query.
+        if (flags & ANDROID_RESOLV_NO_CACHE_STORE) {
+            result = RESOLV_CACHE_SKIP;
+            goto Exit;
+        }
         // calling thread will wait if an outstanding request is found
         // that matching this query
         if (!_cache_check_pending_request_locked(&cache, key, netid) || cache == NULL) {
diff --git a/resolv/res_query.cpp b/resolv/res_query.cpp
index f81b33e..4ae13bf 100644
--- a/resolv/res_query.cpp
+++ b/resolv/res_query.cpp
@@ -140,7 +140,7 @@
         *herrno = NO_RECOVERY;
         return n;
     }
-    n = res_nsend(statp, buf, n, answer, anslen, &rcode);
+    n = res_nsend(statp, buf, n, answer, anslen, &rcode, 0);
     if (n < 0) {
         /* if the query choked with EDNS0, retry without EDNS0 */
         if ((statp->options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) != 0U &&
diff --git a/resolv/res_send.cpp b/resolv/res_send.cpp
index a8b2aeb..21017ac 100644
--- a/resolv/res_send.cpp
+++ b/resolv/res_send.cpp
@@ -388,7 +388,8 @@
     return (1);
 }
 
-int res_nsend(res_state statp, const u_char* buf, int buflen, u_char* ans, int anssiz, int* rcode) {
+int res_nsend(res_state statp, const u_char* buf, int buflen, u_char* ans, int anssiz, int* rcode,
+              uint32_t flags) {
     int gotsomewhere, terrno, v_circuit, resplen, n;
     ResolvCacheStatus cache_status = RESOLV_CACHE_UNSUPPORTED;
 
@@ -404,7 +405,7 @@
     terrno = ETIMEDOUT;
 
     int anslen = 0;
-    cache_status = _resolv_cache_lookup(statp->netid, buf, buflen, ans, anssiz, &anslen);
+    cache_status = _resolv_cache_lookup(statp->netid, buf, buflen, ans, anssiz, &anslen, flags);
 
     if (cache_status == RESOLV_CACHE_FOUND) {
         return anslen;
@@ -417,7 +418,7 @@
         // We have no nameservers configured, so there's no point trying.
         // Tell the cache the query failed, or any retries and anyone else asking the same
         // question will block for PENDING_REQUEST_TIMEOUT seconds instead of failing fast.
-        _resolv_cache_query_failed(statp->netid, buf, buflen);
+        _resolv_cache_query_failed(statp->netid, buf, buflen, flags);
 
         // TODO: Remove errno once callers stop using it
         errno = ESRCH;
@@ -507,7 +508,9 @@
     /*
      * Send request, RETRY times, or until successful.
      */
-    for (int attempt = 0; attempt < statp->retry; ++attempt) {
+    int retryTimes = (flags & ANDROID_RESOLV_NO_RETRY) ? 1 : statp->retry;
+
+    for (int attempt = 0; attempt < retryTimes; ++attempt) {
         struct res_stats stats[MAXNS];
         struct __res_params params;
         int revision_id = resolv_cache_get_resolver_stats(statp->netid, &params, stats);
@@ -539,7 +542,7 @@
                     return resplen;
                 }
                 if (!fallback) {
-                    _resolv_cache_query_failed(statp->netid, buf, buflen);
+                    _resolv_cache_query_failed(statp->netid, buf, buflen, flags);
                     res_nclose(statp);
                     return -terrno;
                 }
@@ -554,7 +557,8 @@
 
             if (v_circuit) {
                 /* Use VC; at most one attempt per server. */
-                attempt = statp->retry;
+                bool shouldRecordStats = (attempt == 0);
+                attempt = retryTimes;
 
                 n = send_vc(statp, &params, buf, buflen, ans, anssiz, &terrno, ns, &now, rcode,
                             &delay);
@@ -564,7 +568,7 @@
                  * queries that deterministically fail (e.g., a name that always returns
                  * SERVFAIL or times out) do not unduly affect the stats.
                  */
-                if (attempt == 0) {
+                if (shouldRecordStats) {
                     res_sample sample;
                     _res_stats_set_sample(&sample, now, *rcode, delay);
                     _resolv_cache_add_resolver_stats_sample(statp->netid, revision_id, ns, &sample,
@@ -574,7 +578,7 @@
                 VLOG << "used send_vc " << n;
 
                 if (n < 0) {
-                    _resolv_cache_query_failed(statp->netid, buf, buflen);
+                    _resolv_cache_query_failed(statp->netid, buf, buflen, flags);
                     res_nclose(statp);
                     return -terrno;
                 };
@@ -598,7 +602,7 @@
                 VLOG << "used send_dg " << n;
 
                 if (n < 0) {
-                    _resolv_cache_query_failed(statp->netid, buf, buflen);
+                    _resolv_cache_query_failed(statp->netid, buf, buflen, flags);
                     res_nclose(statp);
                     return -terrno;
                 };
@@ -645,7 +649,7 @@
     } else {
         errno = terrno;
     }
-    _resolv_cache_query_failed(statp->netid, buf, buflen);
+    _resolv_cache_query_failed(statp->netid, buf, buflen, flags);
 
     return -terrno;
 }
@@ -1314,11 +1318,11 @@
     }
 }
 
-int resolv_res_nsend(const android_net_context* netContext, const u_char* msg, int msgLen,
-                     u_char* ans, int ansLen, int* rcode) {
+int resolv_res_nsend(const android_net_context* netContext, const uint8_t* msg, int msgLen,
+                     uint8_t* ans, int ansLen, int* rcode, uint32_t flags) {
     res_state res = res_get_state();
     res_setnetcontext(res, netContext);
     _resolv_populate_res_for_net(res);
     *rcode = NOERROR;
-    return res_nsend(res, msg, msgLen, ans, ansLen, rcode);
-}
+    return res_nsend(res, msg, msgLen, ans, ansLen, rcode, flags);
+}
\ No newline at end of file
diff --git a/resolv/resolv_cache.h b/resolv/resolv_cache.h
index dd8aa80..4cfa119 100644
--- a/resolv/resolv_cache.h
+++ b/resolv/resolv_cache.h
@@ -28,6 +28,8 @@
 #ifndef NETD_RESOLV_CACHE_H
 #define NETD_RESOLV_CACHE_H
 
+#include "netd_resolv/resolv.h"
+
 #include <stddef.h>
 
 struct __res_state;
@@ -41,12 +43,13 @@
     RESOLV_CACHE_UNSUPPORTED, /* the cache can't handle that kind of queries */
                               /* or the answer buffer is too small */
     RESOLV_CACHE_NOTFOUND,    /* the cache doesn't know about this query */
-    RESOLV_CACHE_FOUND        /* the cache found the answer */
+    RESOLV_CACHE_FOUND,       /* the cache found the answer */
+    RESOLV_CACHE_SKIP         /* Don't do anything on cache */
 } ResolvCacheStatus;
 
 ResolvCacheStatus _resolv_cache_lookup(unsigned netid, const void* query, int querylen,
-                                       void* answer, int answersize,
-                                       int* answerlen);
+                                       void* answer, int answersize, int* answerlen,
+                                       uint32_t flags);
 
 /* add a (query,answer) to the cache, only call if _resolv_cache_lookup
  * did return RESOLV_CACHE_NOTFOUND
@@ -55,6 +58,6 @@
                        int answerlen);
 
 /* Notify the cache a request failed */
-void _resolv_cache_query_failed(unsigned netid, const void* query, int querylen);
+void _resolv_cache_query_failed(unsigned netid, const void* query, int querylen, uint32_t flags);
 
 #endif  // NETD_RESOLV_CACHE_H
diff --git a/resolv/resolv_private.h b/resolv/resolv_private.h
index d38fb43..3dfb999 100644
--- a/resolv/resolv_private.h
+++ b/resolv/resolv_private.h
@@ -239,7 +239,7 @@
 int res_nquerydomain(res_state, const char*, const char*, int, int, u_char*, int, int*);
 int res_nmkquery(res_state, int, const char*, int, int, const u_char*, int, const u_char*, u_char*,
                  int);
-int res_nsend(res_state, const u_char*, int, u_char*, int, int*);
+int res_nsend(res_state, const u_char*, int, u_char*, int, int*, uint32_t);
 void res_nclose(res_state);
 int res_nopt(res_state, int, u_char*, int, int);
 int res_vinit(res_state, int);
diff --git a/resolv/resolver_test.cpp b/resolv/resolver_test.cpp
index 5c5d833..959b1ed 100644
--- a/resolv/resolver_test.cpp
+++ b/resolv/resolver_test.cpp
@@ -37,6 +37,7 @@
 #include <thread>
 
 #include <android-base/stringprintf.h>
+#include <android/multinetwork.h>  // ResNsendFlags
 #include <cutils/sockets.h>
 #include <gtest/gtest.h>
 #include <openssl/base64.h>
@@ -1577,6 +1578,10 @@
     revents = wait_fd[0].revents;
     if (revents & POLLIN) {
         int n = resNetworkResult(fd, rcode, buf, bufLen);
+        // Verify that resNetworkResult() closed the fd
+        char dummy;
+        EXPECT_EQ(-1, read(fd, &dummy, sizeof dummy));
+        EXPECT_EQ(EBADF, errno);
         return n;
     }
     return -1;
@@ -1622,6 +1627,23 @@
     return s;
 }
 
+void expectAnswersValid(int fd, int ipType, const std::string& expectedAnswer) {
+    int rcode = -1;
+    uint8_t buf[MAXPACKET] = {};
+
+    int res = getAsyncResponse(fd, &rcode, buf, MAXPACKET);
+    EXPECT_GT(res, 0);
+    EXPECT_EQ(expectedAnswer, toString(buf, res, ipType));
+}
+
+void expectAnswersNotValid(int fd, int expectedErrno) {
+    int rcode = -1;
+    uint8_t buf[MAXPACKET] = {};
+
+    int res = getAsyncResponse(fd, &rcode, buf, MAXPACKET);
+    EXPECT_EQ(expectedErrno, res);
+}
+
 }  // namespace
 
 TEST_F(ResolverTest, Async_NormalQueryV4V6) {
@@ -1636,8 +1658,8 @@
     ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains, mDefaultParams_Binder));
     dns.clearQueries();
 
-    int fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", 1, 1, 0);   // Type A       1
-    int fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", 1, 28, 0);  // Type AAAA    28
+    int fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a, 0);
+    int fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_aaaa, 0);
     EXPECT_TRUE(fd1 != -1);
     EXPECT_TRUE(fd2 != -1);
 
@@ -1654,8 +1676,8 @@
     EXPECT_EQ(2U, GetNumQueries(dns, host_name));
 
     // Re-query verify cache works
-    fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", 1, 1, 0);   // Type A       1
-    fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", 1, 28, 0);  // Type AAAA    28
+    fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a, 0);
+    fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_aaaa, 0);
 
     EXPECT_TRUE(fd1 != -1);
     EXPECT_TRUE(fd2 != -1);
@@ -1689,16 +1711,16 @@
         const int queryType;
         const int expectRcode;
     } kTestData[] = {
-            {-1, "", T_AAAA, 0},
-            {-1, "as65ass46", T_AAAA, 0},
-            {-1, "454564564564", T_AAAA, 0},
-            {-1, "h645235", T_A, 0},
-            {-1, "www.google.com", T_A, 0},
+            {-1, "", ns_t_aaaa, 0},
+            {-1, "as65ass46", ns_t_aaaa, 0},
+            {-1, "454564564564", ns_t_aaaa, 0},
+            {-1, "h645235", ns_t_a, 0},
+            {-1, "www.google.com", ns_t_a, 0},
     };
 
     for (auto& td : kTestData) {
         SCOPED_TRACE(td.dname);
-        td.fd = resNetworkQuery(TEST_NETID, td.dname, 1, td.queryType, 0);
+        td.fd = resNetworkQuery(TEST_NETID, td.dname, ns_c_in, td.queryType, 0);
         EXPECT_TRUE(td.fd != -1);
     }
 
@@ -1732,18 +1754,17 @@
     // Wait on the condition variable to ensure that the DNS server has handled our first query.
     {
         std::unique_lock lk(cvMutex);
-        // A 1  AAAA 28
-        fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", 1, 28, 0);
+        fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_aaaa, 0);
         EXPECT_TRUE(fd1 != -1);
         EXPECT_EQ(std::cv_status::no_timeout, cv.wait_for(lk, std::chrono::seconds(1)));
     }
 
     dns.setResponseProbability(0.0);
 
-    int fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", 1, 1, 0);
+    int fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a, 0);
     EXPECT_TRUE(fd2 != -1);
 
-    int fd3 = resNetworkQuery(TEST_NETID, "howdy.example.com", 1, 1, 0);
+    int fd3 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a, 0);
     EXPECT_TRUE(fd3 != -1);
 
     uint8_t buf[MAXPACKET] = {};
@@ -1760,7 +1781,7 @@
 
     dns.setResponseProbability(1.0);
 
-    int fd4 = resNetworkQuery(TEST_NETID, "howdy.example.com", 1, 1, 0);
+    int fd4 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a, 0);
     EXPECT_TRUE(fd4 != -1);
 
     memset(buf, 0, MAXPACKET);
@@ -1794,12 +1815,12 @@
         const std::string cmd;
         const int expectErr;
     } kTestData[] = {
-            // Less arguement
+            // Too few arguments
             {"resnsend " + badMsg + '\0', -EINVAL},
             // Bad netId
-            {"resnsend " + badMsg + " badnetId" + '\0', -EINVAL},
+            {"resnsend badnetId 0 " + badMsg + '\0', -EINVAL},
             // Bad raw data
-            {"resnsend " + badMsg + " " + std::to_string(TEST_NETID) + '\0', -EILSEQ},
+            {"resnsend " + std::to_string(TEST_NETID) + " 0 " + badMsg + '\0', -EILSEQ},
     };
 
     for (unsigned int i = 0; i < std::size(kTestData); i++) {
@@ -1816,14 +1837,14 @@
     // Normal query with answer buffer
     // This is raw data of query "howdy.example.com" type 1 class 1
     std::string query = "81sBAAABAAAAAAAABWhvd2R5B2V4YW1wbGUDY29tAAABAAE=";
-    std::string cmd = "resnsend " + query + " " + std::to_string(TEST_NETID) + '\0';
+    std::string cmd = "resnsend " + std::to_string(TEST_NETID) + " 0 " + query + '\0';
     ssize_t rc = TEMP_FAILURE_RETRY(write(fd, cmd.c_str(), cmd.size()));
     EXPECT_EQ(rc, static_cast<ssize_t>(cmd.size()));
 
     u_char smallBuf[1] = {};
     int rcode;
     rc = getAsyncResponse(fd, &rcode, smallBuf, 1);
-    EXPECT_EQ(rc, -EMSGSIZE);
+    EXPECT_EQ(-EMSGSIZE, rc);
 
     // Do the normal test with large buffer again
     fd = dns_open_proxy();
@@ -1835,6 +1856,116 @@
     EXPECT_EQ("1.2.3.4", toString(buf, rc, AF_INET));
 }
 
+TEST_F(ResolverTest, Async_CacheFlags) {
+    const char listen_addr[] = "127.0.0.4";
+    const char listen_srv[] = "53";
+    const char host_name[] = "howdy.example.com.";
+    test::DNSResponder dns(listen_addr, listen_srv, 250, ns_rcode::ns_r_servfail);
+    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
+    dns.addMapping(host_name, ns_type::ns_t_aaaa, "::1.2.3.4");
+    ASSERT_TRUE(dns.startServer());
+    std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains, mDefaultParams_Binder));
+    dns.clearQueries();
+
+    // ANDROID_RESOLV_NO_CACHE_STORE
+    int fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a,
+                              ANDROID_RESOLV_NO_CACHE_STORE);
+    EXPECT_TRUE(fd1 != -1);
+    int fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a,
+                              ANDROID_RESOLV_NO_CACHE_STORE);
+    EXPECT_TRUE(fd2 != -1);
+    int fd3 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a,
+                              ANDROID_RESOLV_NO_CACHE_STORE);
+    EXPECT_TRUE(fd3 != -1);
+
+    expectAnswersValid(fd3, AF_INET, "1.2.3.4");
+    expectAnswersValid(fd2, AF_INET, "1.2.3.4");
+    expectAnswersValid(fd1, AF_INET, "1.2.3.4");
+
+    // No cache exists, expect 3 queries
+    EXPECT_EQ(3U, GetNumQueries(dns, host_name));
+
+    // Re-query and cache
+    fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a, 0);
+
+    EXPECT_TRUE(fd1 != -1);
+
+    expectAnswersValid(fd1, AF_INET, "1.2.3.4");
+
+    // Now we have cache, expect 4 queries
+    EXPECT_EQ(4U, GetNumQueries(dns, host_name));
+
+    // ANDROID_RESOLV_NO_CACHE_LOOKUP
+    fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a,
+                          ANDROID_RESOLV_NO_CACHE_LOOKUP);
+    fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a,
+                          ANDROID_RESOLV_NO_CACHE_LOOKUP);
+
+    EXPECT_TRUE(fd1 != -1);
+    EXPECT_TRUE(fd2 != -1);
+
+    expectAnswersValid(fd2, AF_INET, "1.2.3.4");
+    expectAnswersValid(fd1, AF_INET, "1.2.3.4");
+
+    // Skip cache, expect 6 queries
+    EXPECT_EQ(6U, GetNumQueries(dns, host_name));
+
+    // Re-query verify cache works
+    fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a,
+                          ANDROID_RESOLV_NO_CACHE_STORE);
+    EXPECT_TRUE(fd1 != -1);
+    expectAnswersValid(fd1, AF_INET, "1.2.3.4");
+
+    // Cache hits,  expect still 6 queries
+    EXPECT_EQ(6U, GetNumQueries(dns, host_name));
+}
+
+TEST_F(ResolverTest, Async_NoRetryFlag) {
+    const char listen_addr[] = "127.0.0.4";
+    const char listen_srv[] = "53";
+    const char host_name[] = "howdy.example.com.";
+    test::DNSResponder dns(listen_addr, listen_srv, 250, static_cast<ns_rcode>(-1));
+    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
+    dns.addMapping(host_name, ns_type::ns_t_aaaa, "::1.2.3.4");
+    ASSERT_TRUE(dns.startServer());
+    std::vector<std::string> servers = {listen_addr};
+    ASSERT_TRUE(SetResolversForNetwork(servers, mDefaultSearchDomains, mDefaultParams_Binder));
+    dns.clearQueries();
+
+    dns.setResponseProbability(0.0);
+
+    int fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a,
+                              ANDROID_RESOLV_NO_RETRY);
+    EXPECT_TRUE(fd1 != -1);
+
+    int fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_aaaa,
+                              ANDROID_RESOLV_NO_RETRY);
+    EXPECT_TRUE(fd2 != -1);
+
+    // expect no response
+    expectAnswersNotValid(fd1, -ETIMEDOUT);
+    expectAnswersNotValid(fd2, -ETIMEDOUT);
+
+    // No retry case, expect 2 queries
+    EXPECT_EQ(2U, GetNumQueries(dns, host_name));
+
+    dns.clearQueries();
+
+    fd1 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_a, 0);
+    EXPECT_TRUE(fd1 != -1);
+
+    fd2 = resNetworkQuery(TEST_NETID, "howdy.example.com", ns_c_in, ns_t_aaaa, 0);
+    EXPECT_TRUE(fd2 != -1);
+
+    // expect no response
+    expectAnswersNotValid(fd1, -ETIMEDOUT);
+    expectAnswersNotValid(fd2, -ETIMEDOUT);
+
+    // Retry case, expect 4 queries
+    EXPECT_EQ(4U, GetNumQueries(dns, host_name));
+}
+
 // This test checks that the resolver should not generate the request containing OPT RR when using
 // cleartext DNS. If we query the DNS server not supporting EDNS0 and it reponds with FORMERR, we
 // will fallback to no EDNS0 and try again. If the server does no response, we won't retry so that