Snap for 7080740 from d8420baacf39ac23a048bc83bc2c95f8a3d061d7 to mainline-networkstack-release

Change-Id: I0e5f971bfed2c4239cfd64b9772adaecc58a4d32
diff --git a/Android.bp b/Android.bp
index db610a6..71228b2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -13,6 +13,10 @@
 cc_library_headers {
     name: "dnsproxyd_protocol_headers",
     export_include_dirs: ["include/dnsproxyd_protocol"],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.tethering",
+    ],
 }
 
 aidl_interface {
@@ -23,6 +27,8 @@
         "binder/android/net/ResolverHostsParcel.aidl",
         "binder/android/net/ResolverOptionsParcel.aidl",
         "binder/android/net/ResolverParamsParcel.aidl",
+        // New AIDL classes should go into android.net.resolv.aidl so they can be clearly identified
+        "binder/android/net/resolv/aidl/**/*.aidl",
     ],
     imports: [
         "netd_event_listener_interface",
@@ -50,6 +56,7 @@
         "4",
         "5",
         "6",
+        "7",
     ],
 }
 
@@ -101,7 +108,6 @@
         "res_cache.cpp",
         "res_comp.cpp",
         "res_debug.cpp",
-        "res_init.cpp",
         "res_mkquery.cpp",
         "res_query.cpp",
         "res_send.cpp",
@@ -259,6 +265,8 @@
         "DnsQueryLogTest.cpp",
         "DnsStatsTest.cpp",
         "ExperimentsTest.cpp",
+        "OperationLimiterTest.cpp",
+        "PrivateDnsConfigurationTest.cpp",
     ],
     shared_libs: [
         "libcrypto",
@@ -268,7 +276,7 @@
     static_libs: [
         "dnsresolver_aidl_interface-unstable-ndk_platform",
         "netd_aidl_interface-ndk_platform",
-        "netd_event_listener_interface-ndk_platform",
+        "netd_event_listener_interface-unstable-ndk_platform",
         "libcutils",
         "libgmock",
         "libnetd_resolv",
diff --git a/DnsProxyListener.cpp b/DnsProxyListener.cpp
index df5d873..d7fbcfc 100644
--- a/DnsProxyListener.cpp
+++ b/DnsProxyListener.cpp
@@ -39,7 +39,6 @@
 #include <cutils/misc.h>           // FIRST_APPLICATION_UID
 #include <cutils/multiuser.h>
 #include <netdutils/InternetAddresses.h>
-#include <netdutils/OperationLimiter.h>
 #include <netdutils/ResponseCode.h>
 #include <netdutils/Slice.h>
 #include <netdutils/Stopwatch.h>
@@ -50,6 +49,7 @@
 
 #include "DnsResolver.h"
 #include "NetdPermissions.h"
+#include "OperationLimiter.h"
 #include "PrivateDnsConfiguration.h"
 #include "ResolverEventReporter.h"
 #include "dnsproxyd_protocol/DnsProxydProtocol.h"  // NETID_USE_LOCAL_NAMESERVERS
@@ -84,25 +84,6 @@
     }
 }
 
-template<typename T>
-void tryThreadOrError(SocketClient* cli, T* handler) {
-    cli->incRef();
-
-    const int rval = netdutils::threadLaunch(handler);
-    if (rval == 0) {
-        // SocketClient decRef() happens in the handler's run() method.
-        return;
-    }
-
-    char* msg = nullptr;
-    asprintf(&msg, "%s (%d)", strerror(-rval), -rval);
-    cli->sendMsg(ResponseCode::OperationFailed, msg, false);
-    free(msg);
-
-    delete handler;
-    cli->decRef();
-}
-
 bool checkAndClearUseLocalNameserversFlag(unsigned* netid) {
     if (netid == nullptr || ((*netid) & NETID_USE_LOCAL_NAMESERVERS) == 0) {
         return false;
@@ -129,7 +110,7 @@
 
 bool hasPermissionToBypassPrivateDns(uid_t uid) {
     static_assert(AID_SYSTEM >= 0 && AID_SYSTEM < FIRST_APPLICATION_UID,
-        "Calls from AID_SYSTEM must not result in a permission check to avoid deadlock.");
+                  "Calls from AID_SYSTEM must not result in a permission check to avoid deadlock.");
     if (uid >= 0 && uid < FIRST_APPLICATION_UID) {
         return true;
     }
@@ -168,7 +149,7 @@
                            std::vector<std::string>* ip_addrs) {
     int total_ip_addr_count = 0;
     ns_msg handle;
-    if (ns_initparse((const uint8_t*) answer, anslen, &handle) < 0) {
+    if (ns_initparse((const uint8_t*)answer, anslen, &handle) < 0) {
         return 0;
     }
     int ancount = ns_msg_count(handle, ns_s_an);
@@ -181,12 +162,12 @@
         if (ipType == ns_t_a) {
             sockaddr_in sin = {.sin_family = AF_INET};
             memcpy(&sin.sin_addr, rdata, sizeof(sin.sin_addr));
-            addIpAddrWithinLimit(ip_addrs, (sockaddr*) &sin, sizeof(sin));
+            addIpAddrWithinLimit(ip_addrs, (sockaddr*)&sin, sizeof(sin));
             total_ip_addr_count++;
         } else if (ipType == ns_t_aaaa) {
             sockaddr_in6 sin6 = {.sin6_family = AF_INET6};
             memcpy(&sin6.sin6_addr, rdata, sizeof(sin6.sin6_addr));
-            addIpAddrWithinLimit(ip_addrs, (sockaddr*) &sin6, sizeof(sin6));
+            addIpAddrWithinLimit(ip_addrs, (sockaddr*)&sin6, sizeof(sin6));
             total_ip_addr_count++;
         }
     }
@@ -215,17 +196,17 @@
         return 0;
     }
     if (hp->h_addrtype == AF_INET) {
-        in_addr** list = (in_addr**) hp->h_addr_list;
+        in_addr** list = (in_addr**)hp->h_addr_list;
         for (int i = 0; list[i] != nullptr; i++) {
             sockaddr_in sin = {.sin_family = AF_INET, .sin_addr = *list[i]};
-            addIpAddrWithinLimit(ip_addrs, (sockaddr*) &sin, sizeof(sin));
+            addIpAddrWithinLimit(ip_addrs, (sockaddr*)&sin, sizeof(sin));
             total_ip_addr_count++;
         }
     } else if (hp->h_addrtype == AF_INET6) {
-        in6_addr** list = (in6_addr**) hp->h_addr_list;
+        in6_addr** list = (in6_addr**)hp->h_addr_list;
         for (int i = 0; list[i] != nullptr; i++) {
             sockaddr_in6 sin6 = {.sin6_family = AF_INET6, .sin6_addr = *list[i]};
-            addIpAddrWithinLimit(ip_addrs, (sockaddr*) &sin6, sizeof(sin6));
+            addIpAddrWithinLimit(ip_addrs, (sockaddr*)&sin6, sizeof(sin6));
             total_ip_addr_count++;
         }
     }
@@ -401,7 +382,7 @@
 bool isSpecialUseIPv4Address(const struct sockaddr* sa) {
     if (sa->sa_family != AF_INET) return false;
 
-    return isSpecialUseIPv4Address(((struct sockaddr_in*) sa)->sin_addr);
+    return isSpecialUseIPv4Address(((struct sockaddr_in*)sa)->sin_addr);
 }
 
 bool onlyNonSpecialUseIPv4Addresses(struct hostent* hp) {
@@ -410,7 +391,7 @@
     if (hp->h_addrtype != AF_INET) return false;
 
     for (int i = 0; hp->h_addr_list[i] != nullptr; i++)
-        if (isSpecialUseIPv4Address(*(struct in_addr*) hp->h_addr_list[i])) return false;
+        if (isSpecialUseIPv4Address(*(struct in_addr*)hp->h_addr_list[i])) return false;
 
     return true;
 }
@@ -480,10 +461,10 @@
     if (!isValidNat64Prefix(prefix)) return false;
 
     struct sockaddr_storage ss = netdutils::IPSockAddr(prefix.ip());
-    struct sockaddr_in6* v6prefix = (struct sockaddr_in6*) &ss;
+    struct sockaddr_in6* v6prefix = (struct sockaddr_in6*)&ss;
     for (int i = 0; hp->h_addr_list[i] != nullptr; i++) {
-        struct in_addr iaOriginal = *(struct in_addr*) hp->h_addr_list[i];
-        struct in6_addr* ia6 = (struct in6_addr*) hp->h_addr_list[i];
+        struct in_addr iaOriginal = *(struct in_addr*)hp->h_addr_list[i];
+        struct in6_addr* ia6 = (struct in6_addr*)hp->h_addr_list[i];
         memset(ia6, 0, sizeof(struct in6_addr));
 
         // Synthesize /96 NAT64 prefix in place. The space has reserved by getanswer() and
@@ -515,10 +496,10 @@
     if (!isValidNat64Prefix(prefix)) return false;
 
     struct sockaddr_storage ss = netdutils::IPSockAddr(prefix.ip());
-    struct sockaddr_in6* v6prefix = (struct sockaddr_in6*) &ss;
+    struct sockaddr_in6* v6prefix = (struct sockaddr_in6*)&ss;
     for (addrinfo* ai = result; ai; ai = ai->ai_next) {
-        struct sockaddr_in sinOriginal = *(struct sockaddr_in*) ai->ai_addr;
-        struct sockaddr_in6* sin6 = (struct sockaddr_in6*) ai->ai_addr;
+        struct sockaddr_in sinOriginal = *(struct sockaddr_in*)ai->ai_addr;
+        struct sockaddr_in6* sin6 = (struct sockaddr_in6*)ai->ai_addr;
         memset(sin6, 0, sizeof(sockaddr_in6));
 
         // Synthesize /96 NAT64 prefix in place. The space has reserved by get_ai() in
@@ -563,21 +544,31 @@
     registerCmd(new GetDnsNetIdCommand());
 }
 
-DnsProxyListener::GetAddrInfoHandler::GetAddrInfoHandler(SocketClient* c, char* host, char* service,
-                                                         addrinfo* hints,
-                                                         const android_net_context& netcontext)
-    : mClient(c), mHost(host), mService(service), mHints(hints), mNetContext(netcontext) {}
+void DnsProxyListener::Handler::spawn() {
+    const int rval = netdutils::threadLaunch(this);
+    if (rval == 0) {
+        return;
+    }
 
-DnsProxyListener::GetAddrInfoHandler::~GetAddrInfoHandler() {
-    free(mHost);
-    free(mService);
-    free(mHints);
+    char* msg = nullptr;
+    asprintf(&msg, "%s (%d)", strerror(-rval), -rval);
+    mClient->sendMsg(ResponseCode::OperationFailed, msg, false);
+    free(msg);
+    delete this;
 }
 
-static bool evaluate_domain_name(const android_net_context &netcontext,
-                                 const char *host) {
-    if (!gResNetdCallbacks.evaluate_domain_name)
-        return true;
+DnsProxyListener::GetAddrInfoHandler::GetAddrInfoHandler(SocketClient* c, std::string host,
+                                                         std::string service,
+                                                         std::unique_ptr<addrinfo> hints,
+                                                         const android_net_context& netcontext)
+    : Handler(c),
+      mHost(move(host)),
+      mService(move(service)),
+      mHints(move(hints)),
+      mNetContext(netcontext) {}
+
+static bool evaluate_domain_name(const android_net_context& netcontext, const char* host) {
+    if (!gResNetdCallbacks.evaluate_domain_name) return true;
     return gResNetdCallbacks.evaluate_domain_name(netcontext, host);
 }
 
@@ -597,15 +588,15 @@
     bool success = true;
     int i;
     if (hp->h_name != nullptr) {
-        success &= sendLenAndData(c, strlen(hp->h_name)+1, hp->h_name);
+        success &= sendLenAndData(c, strlen(hp->h_name) + 1, hp->h_name);
     } else {
         success &= sendLenAndData(c, 0, "") == 0;
     }
 
-    for (i=0; hp->h_aliases[i] != nullptr; i++) {
-        success &= sendLenAndData(c, strlen(hp->h_aliases[i])+1, hp->h_aliases[i]);
+    for (i = 0; hp->h_aliases[i] != nullptr; i++) {
+        success &= sendLenAndData(c, strlen(hp->h_aliases[i]) + 1, hp->h_aliases[i]);
     }
-    success &= sendLenAndData(c, 0, ""); // null to indicate we're done
+    success &= sendLenAndData(c, 0, "");  // null to indicate we're done
 
     uint32_t buf = htonl(hp->h_addrtype);
     success &= c->sendData(&buf, sizeof(buf)) == 0;
@@ -613,10 +604,10 @@
     buf = htonl(hp->h_length);
     success &= c->sendData(&buf, sizeof(buf)) == 0;
 
-    for (i=0; hp->h_addr_list[i] != nullptr; i++) {
+    for (i = 0; hp->h_addr_list[i] != nullptr; i++) {
         success &= sendLenAndData(c, 16, hp->h_addr_list[i]);
     }
-    success &= sendLenAndData(c, 0, ""); // null to indicate we're done
+    success &= sendLenAndData(c, 0, "");  // null to indicate we're done
     return success;
 }
 
@@ -634,11 +625,8 @@
 
     // Write the struct piece by piece because we might be a 64-bit netd
     // talking to a 32-bit process.
-    bool success =
-            sendBE32(c, ai->ai_flags) &&
-            sendBE32(c, ai->ai_family) &&
-            sendBE32(c, ai->ai_socktype) &&
-            sendBE32(c, ai->ai_protocol);
+    bool success = sendBE32(c, ai->ai_flags) && sendBE32(c, ai->ai_family) &&
+                   sendBE32(c, ai->ai_socktype) && sendBE32(c, ai->ai_protocol);
     if (!success) {
         return false;
     }
@@ -658,8 +646,6 @@
 
 void DnsProxyListener::GetAddrInfoHandler::doDns64Synthesis(int32_t* rv, addrinfo** res,
                                                             NetworkDnsEventReported* event) {
-    if (mHost == nullptr) return;
-
     const bool ipv6WantedButNoData = (mHints && mHints->ai_family == AF_INET6 && *rv == EAI_NODATA);
     const bool unspecWantedButNoIPv6 =
             ((!mHints || mHints->ai_family == AF_UNSPEC) && *rv == 0 && onlyIPv4Answers(*res));
@@ -677,10 +663,12 @@
         // If caller wants IPv6 answers but no data, try to query IPv4 answers for synthesis
         const uid_t uid = mClient->getUid();
         if (queryLimiter.start(uid)) {
+            const char* host = mHost.starts_with('^') ? nullptr : mHost.c_str();
+            const char* service = mService.starts_with('^') ? nullptr : mService.c_str();
             mHints->ai_family = AF_INET;
             // Don't need to do freeaddrinfo(res) before starting new DNS lookup because previous
             // DNS lookup is failed with error EAI_NODATA.
-            *rv = resolv_getaddrinfo(mHost, mService, mHints, &mNetContext, res, event);
+            *rv = resolv_getaddrinfo(host, service, mHints.get(), &mNetContext, res, event);
             queryLimiter.finish(uid);
             if (*rv) {
                 *rv = EAI_NODATA;  // return original error code
@@ -718,9 +706,10 @@
     NetworkDnsEventReported event;
     initDnsEvent(&event, mNetContext);
     if (queryLimiter.start(uid)) {
-        if (evaluate_domain_name(mNetContext, mHost)) {
-            rv = resolv_getaddrinfo(mHost, mService, mHints, &mNetContext, &result,
-                                    &event);
+        const char* host = mHost.starts_with('^') ? nullptr : mHost.c_str();
+        const char* service = mService.starts_with('^') ? nullptr : mService.c_str();
+        if (evaluate_domain_name(mNetContext, host)) {
+            rv = resolv_getaddrinfo(host, service, mHints.get(), &mNetContext, &result, &event);
         } else {
             rv = EAI_SYSTEM;
         }
@@ -763,7 +752,6 @@
     reportDnsEvent(INetdEventListener::EVENT_GETADDRINFO, mNetContext, latencyUs, rv, event, mHost,
                    ip_addrs, total_ip_addr_count);
     freeaddrinfo(result);
-    mClient->decRef();
 }
 
 std::string DnsProxyListener::GetAddrInfoHandler::threadName() {
@@ -789,34 +777,20 @@
 
 DnsProxyListener::GetAddrInfoCmd::GetAddrInfoCmd() : FrameworkCommand("getaddrinfo") {}
 
-int DnsProxyListener::GetAddrInfoCmd::runCommand(SocketClient *cli,
-                                            int argc, char **argv) {
+int DnsProxyListener::GetAddrInfoCmd::runCommand(SocketClient* cli, int argc, char** argv) {
     logArguments(argc, argv);
 
     if (argc != 8) {
         char* msg = nullptr;
-        asprintf( &msg, "Invalid number of arguments to getaddrinfo: %i", argc);
+        asprintf(&msg, "Invalid number of arguments to getaddrinfo: %i", argc);
         LOG(WARNING) << "GetAddrInfoCmd::runCommand: " << (msg ? msg : "null");
         cli->sendMsg(ResponseCode::CommandParameterError, msg, false);
         free(msg);
         return -1;
     }
 
-    char* name = argv[1];
-    if (strcmp("^", name) == 0) {
-        name = nullptr;
-    } else {
-        name = strdup(name);
-    }
-
-    char* service = argv[2];
-    if (strcmp("^", service) == 0) {
-        service = nullptr;
-    } else {
-        service = strdup(service);
-    }
-
-    addrinfo* hints = nullptr;
+    const std::string name = argv[1];
+    const std::string service = argv[2];
     int ai_flags = strtol(argv[3], nullptr, 10);
     int ai_family = strtol(argv[4], nullptr, 10);
     int ai_socktype = strtol(argv[5], nullptr, 10);
@@ -832,18 +806,16 @@
         netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
     }
 
-    if (ai_flags != -1 || ai_family != -1 ||
-        ai_socktype != -1 || ai_protocol != -1) {
-        hints = (addrinfo*) calloc(1, sizeof(addrinfo));
+    std::unique_ptr<addrinfo> hints;
+    if (ai_flags != -1 || ai_family != -1 || ai_socktype != -1 || ai_protocol != -1) {
+        hints.reset((addrinfo*)calloc(1, sizeof(addrinfo)));
         hints->ai_flags = ai_flags;
         hints->ai_family = ai_family;
         hints->ai_socktype = ai_socktype;
         hints->ai_protocol = ai_protocol;
     }
 
-    DnsProxyListener::GetAddrInfoHandler* handler =
-            new DnsProxyListener::GetAddrInfoHandler(cli, name, service, hints, netcontext);
-    tryThreadOrError(cli, handler);
+    (new GetAddrInfoHandler(cli, name, service, move(hints), netcontext))->spawn();
     return 0;
 }
 
@@ -888,19 +860,13 @@
         netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
     }
 
-    DnsProxyListener::ResNSendHandler* handler =
-            new DnsProxyListener::ResNSendHandler(cli, argv[3], flags, netcontext);
-    tryThreadOrError(cli, handler);
+    (new ResNSendHandler(cli, argv[3], flags, netcontext))->spawn();
     return 0;
 }
 
 DnsProxyListener::ResNSendHandler::ResNSendHandler(SocketClient* c, std::string msg, uint32_t flags,
                                                    const android_net_context& netcontext)
-    : mClient(c), mMsg(std::move(msg)), mFlags(flags), mNetContext(netcontext) {}
-
-DnsProxyListener::ResNSendHandler::~ResNSendHandler() {
-    mClient->decRef();
-}
+    : Handler(c), mMsg(std::move(msg)), mFlags(flags), mNetContext(netcontext) {}
 
 void DnsProxyListener::ResNSendHandler::run() {
     LOG(DEBUG) << "ResNSendHandler::run: " << mFlags << " / {" << mNetContext.app_netid << " "
@@ -991,7 +957,7 @@
     if (rr_type == ns_t_a || rr_type == ns_t_aaaa) {
         std::vector<std::string> ip_addrs;
         const int total_ip_addr_count =
-                extractResNsendAnswers((uint8_t*) ansBuf.data(), nsendAns, rr_type, &ip_addrs);
+                extractResNsendAnswers((uint8_t*)ansBuf.data(), nsendAns, rr_type, &ip_addrs);
         reportDnsEvent(INetdEventListener::EVENT_RES_NSEND, mNetContext, latencyUs,
                        resNSendToAiError(nsendAns, rcode), event, rr_name, ip_addrs,
                        total_ip_addr_count);
@@ -1058,8 +1024,7 @@
  *******************************************************/
 DnsProxyListener::GetHostByNameCmd::GetHostByNameCmd() : FrameworkCommand("gethostbyname") {}
 
-int DnsProxyListener::GetHostByNameCmd::runCommand(SocketClient *cli,
-                                            int argc, char **argv) {
+int DnsProxyListener::GetHostByNameCmd::runCommand(SocketClient* cli, int argc, char** argv) {
     logArguments(argc, argv);
 
     if (argc != 4) {
@@ -1074,15 +1039,9 @@
     uid_t uid = cli->getUid();
     unsigned netId = strtoul(argv[1], nullptr, 10);
     const bool useLocalNameservers = checkAndClearUseLocalNameserversFlag(&netId);
-    char* name = argv[2];
+    std::string name = argv[2];
     int af = strtol(argv[3], nullptr, 10);
 
-    if (strcmp(name, "^") == 0) {
-        name = nullptr;
-    } else {
-        name = strdup(name);
-    }
-
     android_net_context netcontext;
     gResNetdCallbacks.get_network_context(netId, uid, &netcontext);
 
@@ -1090,19 +1049,14 @@
         netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
     }
 
-    DnsProxyListener::GetHostByNameHandler* handler =
-            new DnsProxyListener::GetHostByNameHandler(cli, name, af, netcontext);
-    tryThreadOrError(cli, handler);
+    (new GetHostByNameHandler(cli, name, af, netcontext))->spawn();
     return 0;
 }
 
-DnsProxyListener::GetHostByNameHandler::GetHostByNameHandler(SocketClient* c, char* name, int af,
+DnsProxyListener::GetHostByNameHandler::GetHostByNameHandler(SocketClient* c, std::string name,
+                                                             int af,
                                                              const android_net_context& netcontext)
-    : mClient(c), mName(name), mAf(af), mNetContext(netcontext) {}
-
-DnsProxyListener::GetHostByNameHandler::~GetHostByNameHandler() {
-    free(mName);
-}
+    : Handler(c), mName(move(name)), mAf(af), mNetContext(netcontext) {}
 
 void DnsProxyListener::GetHostByNameHandler::doDns64Synthesis(int32_t* rv, hostent* hbuf, char* buf,
                                                               size_t buflen, struct hostent** hpp,
@@ -1123,7 +1077,8 @@
     // If caller wants IPv6 answers but no data, try to query IPv4 answers for synthesis
     const uid_t uid = mClient->getUid();
     if (queryLimiter.start(uid)) {
-        *rv = resolv_gethostbyname(mName, AF_INET, hbuf, buf, buflen, &mNetContext, hpp, event);
+        const char* name = mName.starts_with('^') ? nullptr : mName.c_str();
+        *rv = resolv_gethostbyname(name, AF_INET, hbuf, buf, buflen, &mNetContext, hpp, event);
         queryLimiter.finish(uid);
         if (*rv) {
             *rv = EAI_NODATA;  // return original error code
@@ -1152,8 +1107,9 @@
     NetworkDnsEventReported event;
     initDnsEvent(&event, mNetContext);
     if (queryLimiter.start(uid)) {
-        if (evaluate_domain_name(mNetContext, mName)) {
-            rv = resolv_gethostbyname(mName, mAf, &hbuf, tmpbuf, sizeof tmpbuf, &mNetContext, &hp,
+        const char* name = mName.starts_with('^') ? nullptr : mName.c_str();
+        if (evaluate_domain_name(mNetContext, name)) {
+            rv = resolv_gethostbyname(name, mAf, &hbuf, tmpbuf, sizeof tmpbuf, &mNetContext, &hp,
                                       &event);
         } else {
             rv = EAI_SYSTEM;
@@ -1190,7 +1146,6 @@
     const int total_ip_addr_count = extractGetHostByNameAnswers(hp, &ip_addrs);
     reportDnsEvent(INetdEventListener::EVENT_GETHOSTBYNAME, mNetContext, latencyUs, rv, event,
                    mName, ip_addrs, total_ip_addr_count);
-    mClient->decRef();
 }
 
 std::string DnsProxyListener::GetHostByNameHandler::threadName() {
@@ -1202,8 +1157,7 @@
  *******************************************************/
 DnsProxyListener::GetHostByAddrCmd::GetHostByAddrCmd() : FrameworkCommand("gethostbyaddr") {}
 
-int DnsProxyListener::GetHostByAddrCmd::runCommand(SocketClient *cli,
-                                            int argc, char **argv) {
+int DnsProxyListener::GetHostByAddrCmd::runCommand(SocketClient* cli, int argc, char** argv) {
     logArguments(argc, argv);
 
     if (argc != 5) {
@@ -1222,15 +1176,14 @@
     unsigned netId = strtoul(argv[4], nullptr, 10);
     const bool useLocalNameservers = checkAndClearUseLocalNameserversFlag(&netId);
 
-    void* addr = malloc(sizeof(in6_addr));
+    in6_addr addr;
     errno = 0;
-    int result = inet_pton(addrFamily, addrStr, addr);
+    int result = inet_pton(addrFamily, addrStr, &addr);
     if (result <= 0) {
         char* msg = nullptr;
         asprintf(&msg, "inet_pton(\"%s\") failed %s", addrStr, strerror(errno));
         LOG(WARNING) << "GetHostByAddrCmd::runCommand: " << (msg ? msg : "null");
         cli->sendMsg(ResponseCode::OperationFailed, msg, false);
-        free(addr);
         free(msg);
         return -1;
     }
@@ -1242,30 +1195,24 @@
         netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
     }
 
-    DnsProxyListener::GetHostByAddrHandler* handler = new DnsProxyListener::GetHostByAddrHandler(
-            cli, addr, addrLen, addrFamily, netcontext);
-    tryThreadOrError(cli, handler);
+    (new GetHostByAddrHandler(cli, addr, addrLen, addrFamily, netcontext))->spawn();
     return 0;
 }
 
-DnsProxyListener::GetHostByAddrHandler::GetHostByAddrHandler(SocketClient* c, void* address,
+DnsProxyListener::GetHostByAddrHandler::GetHostByAddrHandler(SocketClient* c, in6_addr address,
                                                              int addressLen, int addressFamily,
                                                              const android_net_context& netcontext)
-    : mClient(c),
+    : Handler(c),
       mAddress(address),
       mAddressLen(addressLen),
       mAddressFamily(addressFamily),
       mNetContext(netcontext) {}
 
-DnsProxyListener::GetHostByAddrHandler::~GetHostByAddrHandler() {
-    free(mAddress);
-}
-
 void DnsProxyListener::GetHostByAddrHandler::doDns64ReverseLookup(hostent* hbuf, char* buf,
                                                                   size_t buflen,
                                                                   struct hostent** hpp,
                                                                   NetworkDnsEventReported* event) {
-    if (*hpp != nullptr || mAddressFamily != AF_INET6 || !mAddress) {
+    if (*hpp != nullptr || mAddressFamily != AF_INET6) {
         return;
     }
 
@@ -1279,8 +1226,8 @@
     }
 
     struct sockaddr_storage ss = netdutils::IPSockAddr(prefix.ip());
-    struct sockaddr_in6* v6prefix = (struct sockaddr_in6*) &ss;
-    struct in6_addr v6addr = *(in6_addr*) mAddress;
+    struct sockaddr_in6* v6prefix = (struct sockaddr_in6*)&ss;
+    struct in6_addr v6addr = mAddress;
     // Check if address has NAT64 prefix. Only /96 IPv6 NAT64 prefixes are supported
     if ((v6addr.s6_addr32[0] != v6prefix->sin6_addr.s6_addr32[0]) ||
         (v6addr.s6_addr32[1] != v6prefix->sin6_addr.s6_addr32[1]) ||
@@ -1320,7 +1267,7 @@
     NetworkDnsEventReported event;
     initDnsEvent(&event, mNetContext);
     if (queryLimiter.start(uid)) {
-        rv = resolv_gethostbyaddr(mAddress, mAddressLen, mAddressFamily, &hbuf, tmpbuf,
+        rv = resolv_gethostbyaddr(&mAddress, mAddressLen, mAddressFamily, &hbuf, tmpbuf,
                                   sizeof tmpbuf, &mNetContext, &hp, &event);
         queryLimiter.finish(uid);
     } else {
@@ -1351,7 +1298,6 @@
 
     reportDnsEvent(INetdEventListener::EVENT_GETHOSTBYADDR, mNetContext, latencyUs, rv, event,
                    (hp && hp->h_name) ? hp->h_name : "null", {}, 0);
-    mClient->decRef();
 }
 
 std::string DnsProxyListener::GetHostByAddrHandler::threadName() {
diff --git a/DnsProxyListener.h b/DnsProxyListener.h
index 34340a0..87f58c8 100644
--- a/DnsProxyListener.h
+++ b/DnsProxyListener.h
@@ -38,6 +38,23 @@
     static constexpr const char* SOCKET_NAME = "dnsproxyd";
 
   private:
+    class Handler {
+      public:
+        Handler(SocketClient* c) : mClient(c) { mClient->incRef(); }
+        virtual ~Handler() { mClient->decRef(); }
+        void operator=(const Handler&) = delete;
+
+        // Attept to spawn the worker thread, or return an error to the client.
+        // The Handler instance will self-delete in either case.
+        void spawn();
+
+        virtual void run() = 0;
+        virtual std::string threadName() = 0;
+
+        SocketClient* mClient;  // ref-counted
+    };
+
+    /* ------ getaddrinfo ------*/
     class GetAddrInfoCmd : public FrameworkCommand {
       public:
         GetAddrInfoCmd();
@@ -45,24 +62,22 @@
         int runCommand(SocketClient* c, int argc, char** argv) override;
     };
 
-    /* ------ getaddrinfo ------*/
-    class GetAddrInfoHandler {
+    class GetAddrInfoHandler : public Handler {
       public:
         // Note: All of host, service, and hints may be NULL
-        GetAddrInfoHandler(SocketClient* c, char* host, char* service, addrinfo* hints,
-                           const android_net_context& netcontext);
-        ~GetAddrInfoHandler();
+        GetAddrInfoHandler(SocketClient* c, std::string host, std::string service,
+                           std::unique_ptr<addrinfo> hints, const android_net_context& netcontext);
+        ~GetAddrInfoHandler() override = default;
 
-        void run();
-        std::string threadName();
+        void run() override;
+        std::string threadName() override;
 
       private:
         void doDns64Synthesis(int32_t* rv, addrinfo** res, NetworkDnsEventReported* event);
 
-        SocketClient* mClient;  // ref counted
-        char* mHost;            // owned. TODO: convert to std::string.
-        char* mService;         // owned. TODO: convert to std::string.
-        addrinfo* mHints;       // owned
+        std::string mHost;
+        std::string mService;
+        std::unique_ptr<addrinfo> mHints;
         android_net_context mNetContext;
     };
 
@@ -74,21 +89,20 @@
         int runCommand(SocketClient* c, int argc, char** argv) override;
     };
 
-    class GetHostByNameHandler {
+    class GetHostByNameHandler : public Handler {
       public:
-        GetHostByNameHandler(SocketClient* c, char* name, int af,
+        GetHostByNameHandler(SocketClient* c, std::string name, int af,
                              const android_net_context& netcontext);
-        ~GetHostByNameHandler();
+        ~GetHostByNameHandler() override = default;
 
-        void run();
-        std::string threadName();
+        void run() override;
+        std::string threadName() override;
 
       private:
         void doDns64Synthesis(int32_t* rv, hostent* hbuf, char* buf, size_t buflen, hostent** hpp,
                               NetworkDnsEventReported* event);
 
-        SocketClient* mClient;  // ref counted
-        char* mName;            // owned. TODO: convert to std::string.
+        std::string mName;
         int mAf;
         android_net_context mNetContext;
     };
@@ -101,21 +115,20 @@
         int runCommand(SocketClient* c, int argc, char** argv) override;
     };
 
-    class GetHostByAddrHandler {
+    class GetHostByAddrHandler : public Handler {
       public:
-        GetHostByAddrHandler(SocketClient* c, void* address, int addressLen, int addressFamily,
+        GetHostByAddrHandler(SocketClient* c, in6_addr address, int addressLen, int addressFamily,
                              const android_net_context& netcontext);
-        ~GetHostByAddrHandler();
+        ~GetHostByAddrHandler() override = default;
 
-        void run();
-        std::string threadName();
+        void run() override;
+        std::string threadName() override;
 
       private:
         void doDns64ReverseLookup(hostent* hbuf, char* buf, size_t buflen, hostent** hpp,
                                   NetworkDnsEventReported* event);
 
-        SocketClient* mClient;  // ref counted
-        void* mAddress;         // address to lookup; owned
+        in6_addr mAddress;
         int mAddressLen;        // length of address to look up
         int mAddressFamily;     // address family
         android_net_context mNetContext;
@@ -129,17 +142,16 @@
         int runCommand(SocketClient* c, int argc, char** argv) override;
     };
 
-    class ResNSendHandler {
+    class ResNSendHandler : public Handler {
       public:
         ResNSendHandler(SocketClient* c, std::string msg, uint32_t flags,
                         const android_net_context& netcontext);
-        ~ResNSendHandler();
+        ~ResNSendHandler() override = default;
 
-        void run();
-        std::string threadName();
+        void run() override;
+        std::string threadName() override;
 
       private:
-        SocketClient* mClient;  // ref counted
         std::string mMsg;
         uint32_t mFlags;
         android_net_context mNetContext;
diff --git a/DnsQueryLog.cpp b/DnsQueryLog.cpp
index 6f0e179..9cc3ca4 100644
--- a/DnsQueryLog.cpp
+++ b/DnsQueryLog.cpp
@@ -17,7 +17,7 @@
 
 #include "DnsQueryLog.h"
 
-#include <android-base/stringprintf.h>
+#include "util.h"
 
 namespace android::net {
 
@@ -45,25 +45,10 @@
     return ret.empty() ? "" : ret.substr(0, ret.length() - 2);
 }
 
-// Return the readable string format "hr:min:sec.ms".
-std::string timestampToString(const std::chrono::system_clock::time_point& ts) {
-    using std::chrono::duration_cast;
-    using std::chrono::milliseconds;
-    const auto time_sec = std::chrono::system_clock::to_time_t(ts);
-    char buf[32];
-    std::strftime(buf, sizeof(buf), "%H:%M:%S", std::localtime(&time_sec));
-    int ms = duration_cast<milliseconds>(ts.time_since_epoch()).count() % 1000;
-    return android::base::StringPrintf("%s.%03d", buf, ms);
-}
-
 }  // namespace
 
 void DnsQueryLog::push(Record&& record) {
-    std::lock_guard guard(mLock);
-    mQueue.push_back(std::move(record));
-    if (mQueue.size() > mCapacity) {
-        mQueue.pop_front();
-    }
+    mQueue.push(std::move(record));
 }
 
 void DnsQueryLog::dump(netdutils::DumpWriter& dw) const {
@@ -71,8 +56,7 @@
     netdutils::ScopedIndent indentStats(dw);
     const auto now = std::chrono::system_clock::now();
 
-    std::lock_guard guard(mLock);
-    for (const auto& record : mQueue) {
+    for (const auto& record : mQueue.copy()) {
         if (now - record.timestamp > mValidityTimeMs) continue;
 
         const std::string maskedHostname = maskHostname(record.hostname);
diff --git a/DnsQueryLog.h b/DnsQueryLog.h
index c19f8db..3e6478e 100644
--- a/DnsQueryLog.h
+++ b/DnsQueryLog.h
@@ -17,16 +17,16 @@
 
 #pragma once
 
-#include <deque>
 #include <string>
 #include <vector>
 
-#include <android-base/thread_annotations.h>
 #include <netdutils/DumpWriter.h>
 
+#include "LockedQueue.h"
+
 namespace android::net {
 
-// A circular buffer based class used for query logging. It's thread-safe for concurrent access.
+// This class stores query records in a locked ring buffer. It's thread-safe for concurrent access.
 class DnsQueryLog {
   public:
     static constexpr std::string_view DUMP_KEYWORD = "querylog";
@@ -52,15 +52,13 @@
     // Allow the tests to set the capacity and the validaty time in milliseconds.
     DnsQueryLog(size_t size = kDefaultLogSize,
                 std::chrono::milliseconds time = kDefaultValidityMinutes)
-        : mCapacity(size), mValidityTimeMs(time) {}
+        : mQueue(size), mValidityTimeMs(time) {}
 
-    void push(Record&& record) EXCLUDES(mLock);
-    void dump(netdutils::DumpWriter& dw) const EXCLUDES(mLock);
+    void push(Record&& record);
+    void dump(netdutils::DumpWriter& dw) const;
 
   private:
-    mutable std::mutex mLock;
-    std::deque<Record> mQueue GUARDED_BY(mLock);
-    const size_t mCapacity;
+    LockedRingBuffer<Record> mQueue;
     const std::chrono::milliseconds mValidityTimeMs;
 
     // The capacity of the circular buffer.
diff --git a/DnsResolver.cpp b/DnsResolver.cpp
index 5068aef..fcd4293 100644
--- a/DnsResolver.cpp
+++ b/DnsResolver.cpp
@@ -20,6 +20,8 @@
 
 #include "DnsProxyListener.h"
 #include "DnsResolverService.h"
+#include "DnsTlsDispatcher.h"
+#include "PrivateDnsConfiguration.h"
 #include "netd_resolv/resolv.h"
 #include "res_debug.h"
 #include "util.h"
@@ -28,7 +30,8 @@
     android::base::InitLogging(/*argv=*/nullptr);
     android::base::SetDefaultTag("libnetd_resolv");
     LOG(INFO) << __func__ << ": Initializing resolver";
-    resolv_set_log_severity(android::base::WARNING);
+    // TODO(b/170539625): restore log level to WARNING after clarifying flaky tests.
+    resolv_set_log_severity(isUserDebugBuild() ? android::base::DEBUG : android::base::WARNING);
     using android::net::gApiLevel;
     gApiLevel = getApiLevel();
     using android::net::gResNetdCallbacks;
@@ -72,6 +75,14 @@
     return &instance;
 }
 
+DnsResolver::DnsResolver() {
+    // TODO: make them member variables after fixing the circular dependency:
+    //   DnsTlsDispatcher.h -> resolv_private.h -> DnsResolver.h -> DnsTlsDispatcher.h
+    auto& dnsTlsDispatcher = DnsTlsDispatcher::getInstance();
+    auto& privateDnsConfiguration = PrivateDnsConfiguration::getInstance();
+    privateDnsConfiguration.setObserver(&dnsTlsDispatcher);
+}
+
 bool DnsResolver::start() {
     if (!verifyCallbacks()) {
         LOG(ERROR) << __func__ << ": Callback verification failed";
diff --git a/DnsResolver.h b/DnsResolver.h
index 863b096..9c2f3d8 100644
--- a/DnsResolver.h
+++ b/DnsResolver.h
@@ -40,7 +40,8 @@
     ResolverController resolverCtrl;
 
   private:
-    DnsResolver() {}
+    DnsResolver();
+
     DnsProxyListener mDnsProxyListener;
     DnsQueryLog mQueryLog;
 };
diff --git a/DnsResolverService.cpp b/DnsResolverService.cpp
index a2eb598..1d6723f 100644
--- a/DnsResolverService.cpp
+++ b/DnsResolverService.cpp
@@ -32,6 +32,7 @@
 #include "DnsResolver.h"
 #include "Experiments.h"
 #include "NetdPermissions.h"  // PERM_*
+#include "PrivateDnsConfiguration.h"
 #include "ResolverEventReporter.h"
 #include "resolv_cache.h"
 
@@ -72,9 +73,9 @@
 
 DnsResolverService::DnsResolverService() {
     // register log callback to BnDnsResolver::logFunc
-    BnDnsResolver::logFunc =
-            std::bind(binderCallLogFn, std::placeholders::_1,
-                      [](const std::string& msg) { gResNetdCallbacks.log(msg.c_str()); });
+    BnDnsResolver::logFunc = [](const auto& log) {
+        binderCallLogFn(log, [](const std::string& msg) { gResNetdCallbacks.log(msg.c_str()); });
+    };
 }
 
 binder_status_t DnsResolverService::start() {
@@ -117,6 +118,8 @@
         gDnsResolv->resolverCtrl.dump(dw, netId);
         dw.blankline();
     }
+
+    PrivateDnsConfiguration::getInstance().dump(dw);
     Experiments::getInstance()->dump(dw);
     return STATUS_OK;
 }
@@ -138,6 +141,14 @@
     return statusFromErrcode(res);
 }
 
+::ndk::ScopedAStatus DnsResolverService::registerUnsolicitedEventListener(
+        const std::shared_ptr<
+                aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener>&) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+
+    return ::ndk::ScopedAStatus(AStatus_newOk());
+}
+
 ::ndk::ScopedAStatus DnsResolverService::checkAnyPermission(
         const std::vector<const char*>& permissions) {
     // TODO: Remove callback and move this to unnamed namespace after libbiner_ndk supports
diff --git a/DnsResolverService.h b/DnsResolverService.h
index 2228e21..fe39301 100644
--- a/DnsResolverService.h
+++ b/DnsResolverService.h
@@ -39,6 +39,10 @@
     ::ndk::ScopedAStatus registerEventListener(
             const std::shared_ptr<aidl::android::net::metrics::INetdEventListener>& listener)
             override;
+    ::ndk::ScopedAStatus registerUnsolicitedEventListener(
+            const std::shared_ptr<
+                    aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener>&
+                    listener) override;
 
     // Resolver commands.
     ::ndk::ScopedAStatus setResolverConfiguration(
diff --git a/DnsStats.cpp b/DnsStats.cpp
index 2f76d8a..c6374c9 100644
--- a/DnsStats.cpp
+++ b/DnsStats.cpp
@@ -110,7 +110,16 @@
 
     // Update the quality factors.
     mSkippedCount = 0;
-    updatePenalty(record);
+
+    // Because failures due to no permission can't prove that the quality of DNS server is bad,
+    // skip the penalty update. The average latency, however, has been updated. For short-latency
+    // servers, it will be fine. For long-latency servers, their average latency will be
+    // decreased but the latency-based algorithm will adjust their average latency back to the
+    // right range after few attempts when network is not restricted.
+    // The check is synced from isNetworkRestricted() in res_send.cpp.
+    if (record.linux_errno != EPERM) {
+        updatePenalty(record);
+    }
 }
 
 void StatsRecords::updateStatsData(const Record& record, const bool add) {
@@ -194,6 +203,7 @@
         if (serverSockAddr == ipSockAddr) {
             const StatsRecords::Record rec = {
                     .rcode = record.rcode(),
+                    .linux_errno = record.linux_errno(),
                     .latencyUs = microseconds(record.latency_micros()),
             };
             statsRecords.push(rec);
diff --git a/DnsStats.h b/DnsStats.h
index c5459b4..39025d3 100644
--- a/DnsStats.h
+++ b/DnsStats.h
@@ -68,7 +68,8 @@
 class StatsRecords {
   public:
     struct Record {
-        int rcode;
+        int rcode = 0;        // NS_R_NO_ERROR
+        int linux_errno = 0;  // SYS_NO_ERROR
         std::chrono::microseconds latencyUs;
     };
 
diff --git a/DnsStatsTest.cpp b/DnsStatsTest.cpp
index 38a7a21..5efd186 100644
--- a/DnsStatsTest.cpp
+++ b/DnsStatsTest.cpp
@@ -59,8 +59,16 @@
 TEST_F(StatsRecordsTest, PushRecord) {
     const IPSockAddr server = IPSockAddr::toIPSockAddr("127.0.0.2", 53);
     constexpr size_t size = 3;
-    const StatsRecords::Record recordNoError = {NS_R_NO_ERROR, 10ms};
-    const StatsRecords::Record recordTimeout = {NS_R_TIMEOUT, 250ms};
+    const StatsRecords::Record recordNoError = {
+            .rcode = NS_R_NO_ERROR,
+            .linux_errno = 0,
+            .latencyUs{10ms},
+    };
+    const StatsRecords::Record recordTimeout = {
+            .rcode = NS_R_TIMEOUT,
+            .linux_errno = 0,
+            .latencyUs{250ms},
+    };
 
     StatsRecords sr(server, size);
     EXPECT_EQ(sr.getStatsData(), makeStatsData(server, 0, 0ms, {}));
@@ -424,6 +432,17 @@
     EXPECT_THAT(mDnsStats.getSortedServers(PROTO_UDP),
                 testing::ElementsAreArray({server2, server3, server1, server4}));
 
+    // Add some internal_error records with permission error to server2.
+    // The internal_error won't cause the priority of server2 drop. (but some of the other
+    // quality factors will still be counted, such as skipped_count and latency)
+    auto recordFromNetworkRestricted = makeDnsQueryEvent(PROTO_UDP, NS_R_INTERNAL_ERROR, 1ms);
+    recordFromNetworkRestricted.set_linux_errno(static_cast<LinuxErrno>(EPERM));
+    for (int i = 0; i < 3; i++) {
+        EXPECT_TRUE(mDnsStats.addStats(server2, recordFromNetworkRestricted));
+    }
+    EXPECT_THAT(mDnsStats.getSortedServers(PROTO_UDP),
+                testing::ElementsAreArray({server2, server3, server1, server4}));
+
     // The list of the DNS servers changed.
     EXPECT_TRUE(mDnsStats.setServers({server2, server4}, PROTO_UDP));
     EXPECT_THAT(mDnsStats.getSortedServers(PROTO_UDP),
diff --git a/DnsTlsDispatcher.cpp b/DnsTlsDispatcher.cpp
index df8fce8..41eac9d 100644
--- a/DnsTlsDispatcher.cpp
+++ b/DnsTlsDispatcher.cpp
@@ -41,6 +41,11 @@
     mFactory.reset(new DnsTlsSocketFactory());
 }
 
+DnsTlsDispatcher& DnsTlsDispatcher::getInstance() {
+    static DnsTlsDispatcher instance;
+    return instance;
+}
+
 std::list<DnsTlsServer> DnsTlsDispatcher::getOrderedServerList(
         const std::list<DnsTlsServer> &tlsServers, unsigned mark) const {
     // Our preferred DnsTlsServer order is:
diff --git a/DnsTlsDispatcher.h b/DnsTlsDispatcher.h
index 9eb6dfe..2cb7519 100644
--- a/DnsTlsDispatcher.h
+++ b/DnsTlsDispatcher.h
@@ -28,6 +28,7 @@
 #include "DnsTlsServer.h"
 #include "DnsTlsTransport.h"
 #include "IDnsTlsSocketFactory.h"
+#include "PrivateDnsValidationObserver.h"
 #include "resolv_private.h"
 
 namespace android {
@@ -35,15 +36,14 @@
 
 // This is a singleton class that manages the collection of active DnsTlsTransports.
 // Queries made here are dispatched to an existing or newly constructed DnsTlsTransport.
-class DnsTlsDispatcher {
+class DnsTlsDispatcher : public PrivateDnsValidationObserver {
   public:
-    // Default constructor.
-    DnsTlsDispatcher();
-
     // Constructor with dependency injection for testing.
     explicit DnsTlsDispatcher(std::unique_ptr<IDnsTlsSocketFactory> factory)
         : mFactory(std::move(factory)) {}
 
+    static DnsTlsDispatcher& getInstance();
+
     // Enqueues |query| for resolution via the given |tlsServers| on the
     // network indicated by |mark|; writes the response into |ans|, and stores
     // the count of bytes written in |resplen|. Returns a success or error code.
@@ -61,7 +61,12 @@
                                     const netdutils::Slice query, const netdutils::Slice ans,
                                     int* _Nonnull resplen, bool* _Nonnull connectTriggered);
 
+    // Implement PrivateDnsValidationObserver.
+    void onValidationStateUpdate(const std::string&, Validation, uint32_t) override{};
+
   private:
+    DnsTlsDispatcher();
+
     // This lock is static so that it can be used to annotate the Transport struct.
     // DnsTlsDispatcher is a singleton in practice, so making this static does not change
     // the locking behavior.
diff --git a/DnsTlsQueryMap.cpp b/DnsTlsQueryMap.cpp
index c4ae450..96191e3 100644
--- a/DnsTlsQueryMap.cpp
+++ b/DnsTlsQueryMap.cpp
@@ -20,9 +20,16 @@
 
 #include <android-base/logging.h>
 
+#include "Experiments.h"
+
 namespace android {
 namespace net {
 
+DnsTlsQueryMap::DnsTlsQueryMap() {
+    mMaxTries = Experiments::getInstance()->getFlag("dot_maxtries", kMaxTries);
+    if (mMaxTries < 1) mMaxTries = 1;
+}
+
 std::unique_ptr<DnsTlsQueryMap::QueryFuture> DnsTlsQueryMap::recordQuery(
         const netdutils::Slice query) {
     std::lock_guard guard(mLock);
@@ -67,7 +74,7 @@
     std::lock_guard guard(mLock);
     for (auto it = mQueries.begin(); it != mQueries.end();) {
         auto& p = it->second;
-        if (p.tries >= kMaxTries) {
+        if (p.tries >= mMaxTries) {
             expire(&p);
             it = mQueries.erase(it);
         } else {
diff --git a/DnsTlsQueryMap.h b/DnsTlsQueryMap.h
index f744234..f3ff963 100644
--- a/DnsTlsQueryMap.h
+++ b/DnsTlsQueryMap.h
@@ -36,6 +36,8 @@
   public:
     enum class Response : uint8_t { success, network_error, limit_error, internal_error };
 
+    DnsTlsQueryMap();
+
     struct Query {
         // The new ID number assigned to this query.
         uint16_t newId;
@@ -80,6 +82,7 @@
 
     // The maximum number of times we will send a query before abandoning it.
     static constexpr int kMaxTries = 3;
+    int mMaxTries;
 
   private:
     std::mutex mLock;
diff --git a/DnsTlsServer.cpp b/DnsTlsServer.cpp
index 4e94488..89ea841 100644
--- a/DnsTlsServer.cpp
+++ b/DnsTlsServer.cpp
@@ -18,6 +18,8 @@
 
 #include <algorithm>
 
+#include <netdutils/InternetAddresses.h>
+
 namespace {
 
 // Returns a tuple of references to the elements of a.
@@ -109,7 +111,7 @@
 
 // Returns a tuple of references to the elements of s.
 auto make_tie(const DnsTlsServer& s) {
-    return std::tie(s.ss, s.name, s.protocol, s.connectTimeout);
+    return std::tie(s.ss, s.name, s.protocol);
 }
 
 bool DnsTlsServer::operator <(const DnsTlsServer& other) const {
@@ -124,5 +126,9 @@
     return !name.empty();
 }
 
+std::string DnsTlsServer::toIpString() const {
+    return netdutils::IPSockAddr::toIPSockAddr(ss).ip().toString();
+}
+
 }  // namespace net
 }  // namespace android
diff --git a/DnsTlsServer.h b/DnsTlsServer.h
index 8484b98..67b0012 100644
--- a/DnsTlsServer.h
+++ b/DnsTlsServer.h
@@ -16,7 +16,6 @@
 
 #pragma once
 
-#include <chrono>
 #include <set>
 #include <string>
 #include <vector>
@@ -25,6 +24,8 @@
 
 #include <params.h>
 
+#include "PrivateDnsCommon.h"
+
 namespace android {
 namespace net {
 
@@ -38,31 +39,48 @@
     DnsTlsServer(const sockaddr_storage& ss) : ss(ss) {}
 
     // The server location, including IP and port.
+    // TODO: make it const.
     sockaddr_storage ss = {};
 
     // The server's hostname.  If this string is nonempty, the server must present a
     // certificate that indicates this name and has a valid chain to a trusted root CA.
+    // TODO: make it const.
     std::string name;
 
     // The certificate of the CA that signed the server's certificate.
     // It is used to store temporary test CA certificate for internal tests.
+    // TODO: make it const.
     std::string certificate;
 
     // Placeholder.  More protocols might be defined in the future.
+    // TODO: make it const.
     int protocol = IPPROTO_TCP;
 
-    // The time to wait for the attempt on connecting to the server.
-    // Set the default value 127 seconds to be consistent with TCP connect timeout.
-    // (presume net.ipv4.tcp_syn_retries = 6)
-    static constexpr std::chrono::milliseconds kDotConnectTimeoutMs =
-            std::chrono::milliseconds(127 * 1000);
-    std::chrono::milliseconds connectTimeout = kDotConnectTimeoutMs;
-
     // Exact comparison of DnsTlsServer objects
     bool operator<(const DnsTlsServer& other) const;
     bool operator==(const DnsTlsServer& other) const;
 
     bool wasExplicitlyConfigured() const;
+    std::string toIpString() const;
+
+    Validation validationState() const { return mValidation; }
+    void setValidationState(Validation val) { mValidation = val; }
+
+    // The socket mark used for validation.
+    // Note that the mark of a connection to which the DnsResolver sends app's DNS requests can
+    // be different.
+    // TODO: make it const.
+    uint32_t mark = 0;
+
+    // Return whether or not the server can be used for a network. It depends on
+    // the resolver configuration.
+    bool active() const { return mActive; }
+    void setActive(bool val) { mActive = val; }
+
+  private:
+    // State, unrelated to the comparison of DnsTlsServer objects.
+    Validation mValidation = Validation::unknown_server;
+    bool mActive = false;
 };
 
 // This comparison only checks the IP address.  It ignores ports, names, and fingerprints.
diff --git a/DnsTlsSocket.cpp b/DnsTlsSocket.cpp
index 19bc139..ad12316 100644
--- a/DnsTlsSocket.cpp
+++ b/DnsTlsSocket.cpp
@@ -37,12 +37,14 @@
 #include <netdutils/SocketOption.h>
 #include <netdutils/ThreadUtil.h>
 
+#include "Experiments.h"
 #include "netd_resolv/resolv.h"
 #include "private/android_filesystem_config.h"  // AID_DNS
 #include "resolv_private.h"
 
 namespace android {
 
+using android::net::Experiments;
 using base::StringPrintf;
 using netdutils::enableSockopt;
 using netdutils::enableTcpKeepAlives;
@@ -81,7 +83,7 @@
 
     mSslFd.reset(socket(mServer.ss.ss_family, type, mServer.protocol));
     if (mSslFd.get() == -1) {
-        LOG(ERROR) << "Failed to create socket";
+        PLOG(ERROR) << "Failed to create socket";
         return Status(errno);
     }
 
@@ -89,9 +91,10 @@
 
     const socklen_t len = sizeof(mMark);
     if (setsockopt(mSslFd.get(), SOL_SOCKET, SO_MARK, &mMark, len) == -1) {
-        LOG(ERROR) << "Failed to set socket mark";
+        const int err = errno;
+        PLOG(ERROR) << "Failed to set socket mark";
         mSslFd.reset();
-        return Status(errno);
+        return Status(err);
     }
 
     const Status tfo = enableSockopt(mSslFd.get(), SOL_TCP, TCP_FASTOPEN_CONNECT);
@@ -105,9 +108,10 @@
     if (connect(mSslFd.get(), reinterpret_cast<const struct sockaddr *>(&mServer.ss),
                 sizeof(mServer.ss)) != 0 &&
             errno != EINPROGRESS) {
-        LOG(DEBUG) << "Socket failed to connect";
+        const int err = errno;
+        PLOG(ERROR) << "Socket failed to connect";
         mSslFd.reset();
-        return Status(errno);
+        return Status(err);
     }
 
     return netdutils::status::ok;
@@ -169,17 +173,42 @@
     // Enable session cache
     mCache->prepareSslContext(mSslCtx.get());
 
-    // Connect
-    Status status = tcpConnect();
-    if (!status.ok()) {
-        return false;
-    }
-    mSsl = sslConnect(mSslFd.get());
-    if (!mSsl) {
-        return false;
-    }
-
     mEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
+    mShutdownEvent.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
+
+    const Experiments* const instance = Experiments::getInstance();
+    mConnectTimeoutMs = instance->getFlag("dot_connect_timeout_ms", kDotConnectTimeoutMs);
+    if (mConnectTimeoutMs < 1000) mConnectTimeoutMs = 1000;
+
+    mAsyncHandshake = instance->getFlag("dot_async_handshake", 0);
+    LOG(DEBUG) << "DnsTlsSocket is initialized with { mConnectTimeoutMs: " << mConnectTimeoutMs
+               << ", mAsyncHandshake: " << mAsyncHandshake << " }";
+
+    transitionState(State::UNINITIALIZED, State::INITIALIZED);
+
+    return true;
+}
+
+bool DnsTlsSocket::startHandshake() {
+    std::lock_guard guard(mLock);
+    if (mState != State::INITIALIZED) {
+        LOG(ERROR) << "Calling startHandshake in unexpected state " << static_cast<int>(mState);
+        return false;
+    }
+    transitionState(State::INITIALIZED, State::CONNECTING);
+
+    if (!mAsyncHandshake) {
+        if (Status status = tcpConnect(); !status.ok()) {
+            transitionState(State::CONNECTING, State::WAIT_FOR_DELETE);
+            LOG(WARNING) << "TCP Handshake failed: " << status.code();
+            return false;
+        }
+        if (mSsl = sslConnect(mSslFd.get()); !mSsl) {
+            transitionState(State::CONNECTING, State::WAIT_FOR_DELETE);
+            LOG(WARNING) << "TLS Handshake failed";
+            return false;
+        }
+    }
 
     // Start the I/O loop.
     mLoopThread.reset(new std::thread(&DnsTlsSocket::loop, this));
@@ -187,7 +216,7 @@
     return true;
 }
 
-bssl::UniquePtr<SSL> DnsTlsSocket::sslConnect(int fd) {
+bssl::UniquePtr<SSL> DnsTlsSocket::prepareForSslConnect(int fd) {
     if (!mSslCtx) {
         LOG(ERROR) << "Internal error: context is null in sslConnect";
         return nullptr;
@@ -230,6 +259,15 @@
         LOG(DEBUG) << "No session available";
     }
 
+    return ssl;
+}
+
+bssl::UniquePtr<SSL> DnsTlsSocket::sslConnect(int fd) {
+    bssl::UniquePtr<SSL> ssl;
+    if (ssl = prepareForSslConnect(fd); !ssl) {
+        return nullptr;
+    }
+
     for (;;) {
         LOG(DEBUG) << " Calling SSL_connect with mark 0x" << std::hex << mMark;
         int ret = SSL_connect(ssl.get());
@@ -242,7 +280,7 @@
                 // SSL_ERROR_WANT_READ is returned because the application data has been sent during
                 // the TCP connection handshake, the device is waiting for the SSL handshake reply
                 // from the server.
-                if (int err = waitForReading(fd, mServer.connectTimeout.count()); err <= 0) {
+                if (int err = waitForReading(fd, mConnectTimeoutMs); err <= 0) {
                     PLOG(WARNING) << "SSL_connect read error " << err << ", mark 0x" << std::hex
                                   << mMark;
                     return nullptr;
@@ -251,7 +289,7 @@
             case SSL_ERROR_WANT_WRITE:
                 // If no application data is sent during the TCP connection handshake, the
                 // device is waiting for the connection established to perform SSL handshake.
-                if (int err = waitForWriting(fd, mServer.connectTimeout.count()); err <= 0) {
+                if (int err = waitForWriting(fd, mConnectTimeoutMs); err <= 0) {
                     PLOG(WARNING) << "SSL_connect write error " << err << ", mark 0x" << std::hex
                                   << mMark;
                     return nullptr;
@@ -269,6 +307,59 @@
     return ssl;
 }
 
+bssl::UniquePtr<SSL> DnsTlsSocket::sslConnectV2(int fd) {
+    bssl::UniquePtr<SSL> ssl;
+    if (ssl = prepareForSslConnect(fd); !ssl) {
+        return nullptr;
+    }
+
+    for (;;) {
+        LOG(DEBUG) << " Calling SSL_connect with mark 0x" << std::hex << mMark;
+        int ret = SSL_connect(ssl.get());
+        LOG(DEBUG) << " SSL_connect returned " << ret << " with mark 0x" << std::hex << mMark;
+        if (ret == 1) break;  // SSL handshake complete;
+
+        enum { SSLFD = 0, EVENTFD = 1 };
+        pollfd fds[2] = {
+                {.fd = mSslFd.get(), .events = 0},
+                {.fd = mShutdownEvent.get(), .events = POLLIN},
+        };
+
+        const int ssl_err = SSL_get_error(ssl.get(), ret);
+        switch (ssl_err) {
+            case SSL_ERROR_WANT_READ:
+                fds[SSLFD].events = POLLIN;
+                break;
+            case SSL_ERROR_WANT_WRITE:
+                fds[SSLFD].events = POLLOUT;
+                break;
+            default:
+                PLOG(WARNING) << "SSL_connect ssl error =" << ssl_err << ", mark 0x" << std::hex
+                              << mMark;
+                return nullptr;
+        }
+
+        int n = TEMP_FAILURE_RETRY(poll(fds, std::size(fds), mConnectTimeoutMs));
+        if (n <= 0) {
+            PLOG(WARNING) << ((n == 0) ? "handshake timeout" : "Poll failed");
+            return nullptr;
+        }
+
+        if (fds[EVENTFD].revents & (POLLIN | POLLERR)) {
+            LOG(WARNING) << "Got shutdown request during handshake";
+            return nullptr;
+        }
+        if (fds[SSLFD].revents & POLLERR) {
+            LOG(WARNING) << "Got POLLERR on SSLFD during handshake";
+            return nullptr;
+        }
+    }
+
+    LOG(DEBUG) << mMark << " handshake complete";
+
+    return ssl;
+}
+
 void DnsTlsSocket::sslDisconnect() {
     if (mSsl) {
         SSL_shutdown(mSsl.get());
@@ -310,6 +401,25 @@
     const int timeout_msecs = DnsTlsSocket::kIdleTimeout.count() * 1000;
 
     setThreadName(StringPrintf("TlsListen_%u", mMark & 0xffff).c_str());
+
+    if (mAsyncHandshake) {
+        if (Status status = tcpConnect(); !status.ok()) {
+            LOG(WARNING) << "TCP Handshake failed: " << status.code();
+            mObserver->onClosed();
+            transitionState(State::CONNECTING, State::WAIT_FOR_DELETE);
+            return;
+        }
+        if (mSsl = sslConnectV2(mSslFd.get()); !mSsl) {
+            LOG(WARNING) << "TLS Handshake failed";
+            mObserver->onClosed();
+            transitionState(State::CONNECTING, State::WAIT_FOR_DELETE);
+            return;
+        }
+        LOG(DEBUG) << "Handshaking succeeded";
+    }
+
+    transitionState(State::CONNECTING, State::CONNECTED);
+
     while (true) {
         // poll() ignores negative fds
         struct pollfd fds[2] = { { .fd = -1 }, { .fd = -1 } };
@@ -336,12 +446,24 @@
             break;
         }
         if (s < 0) {
-            LOG(DEBUG) << "Poll failed: " << errno;
+            PLOG(DEBUG) << "Poll failed";
             break;
         }
         if (fds[SSLFD].revents & (POLLIN | POLLERR | POLLHUP)) {
-            if (!readResponse()) {
-                LOG(DEBUG) << "SSL remote close or read error.";
+            bool readFailed = false;
+
+            // readResponse() only reads one DNS (and consumes exact bytes) from ssl.
+            // Keep doing so until ssl has no pending data.
+            // TODO: readResponse() can block until it reads a complete DNS response. Consider
+            // refactoring it to not get blocked in any case.
+            do {
+                if (!readResponse()) {
+                    LOG(DEBUG) << "SSL remote close or read error.";
+                    readFailed = true;
+                }
+            } while (SSL_pending(mSsl.get()) > 0 && !readFailed);
+
+            if (readFailed) {
                 break;
             }
         }
@@ -379,6 +501,7 @@
     sslDisconnect();
     LOG(DEBUG) << "Calling onClosed";
     mObserver->onClosed();
+    transitionState(State::CONNECTED, State::WAIT_FOR_DELETE);
     LOG(DEBUG) << "Ending loop";
 }
 
@@ -426,6 +549,11 @@
         // Write a negative number to the eventfd.  This triggers an immediate shutdown.
         incrementEventFd(INT64_MIN);
     }
+    if (mShutdownEvent != -1) {
+        if (eventfd_write(mShutdownEvent.get(), INT64_MIN) == -1) {
+            PLOG(ERROR) << "Failed to write to mShutdownEvent";
+        }
+    }
 }
 
 bool DnsTlsSocket::incrementEventFd(const int64_t count) {
@@ -441,6 +569,15 @@
     return true;
 }
 
+void DnsTlsSocket::transitionState(State from, State to) {
+    if (mState != from) {
+        LOG(WARNING) << "BUG: transitioning from an unexpected state " << static_cast<int>(mState)
+                     << ", expect: from " << static_cast<int>(from) << " to "
+                     << static_cast<int>(to);
+    }
+    mState = to;
+}
+
 // Read exactly len bytes into buffer or fail with an SSL error code
 int DnsTlsSocket::sslRead(const Slice buffer, bool wait) {
     size_t remaining = buffer.size();
diff --git a/DnsTlsSocket.h b/DnsTlsSocket.h
index 1439ea9..f6736f8 100644
--- a/DnsTlsSocket.h
+++ b/DnsTlsSocket.h
@@ -44,18 +44,55 @@
 // or the destructor in a callback.  Doing so will result in deadlocks.
 // This class may call the observer at any time after initialize(), until the destructor
 // returns (but not after).
+//
+// Calls to IDnsTlsSocketObserver in a DnsTlsSocket life cycle:
+//
+//                                UNINITIALIZED
+//                                      |
+//                                      v
+//                                 INITIALIZED
+//                                      |
+//                                      v
+//                            +----CONNECTING------+
+//            Handshake fails |                    | Handshake succeeds
+//   (onClose() when          |                    |
+//    mAsyncHandshake is set) |                    v
+//                            |        +---> CONNECTED --+
+//                            |        |           |     |
+//                            |        +-----------+     | Idle timeout
+//                            |   Send/Recv queries      | onClose()
+//                            |   onResponse()           |
+//                            |                          |
+//                            |                          |
+//                            +--> WAIT_FOR_DELETE <-----+
+//
+//
+// TODO: Add onHandshakeFinished() for handshake results.
 class DnsTlsSocket : public IDnsTlsSocket {
   public:
+    enum class State {
+        UNINITIALIZED,
+        INITIALIZED,
+        CONNECTING,
+        CONNECTED,
+        WAIT_FOR_DELETE,
+    };
+
     DnsTlsSocket(const DnsTlsServer& server, unsigned mark,
                  IDnsTlsSocketObserver* _Nonnull observer, DnsTlsSessionCache* _Nonnull cache)
         : mMark(mark), mServer(server), mObserver(observer), mCache(cache) {}
     ~DnsTlsSocket();
 
-    // Creates the SSL context for this session and connect.  Returns false on failure.
+    // Creates the SSL context for this session. Returns false on failure.
     // This method should be called after construction and before use of a DnsTlsSocket.
     // Only call this method once per DnsTlsSocket.
     bool initialize() EXCLUDES(mLock);
 
+    // If async handshake is enabled, this function simply signals a handshake request, and the
+    // handshake will be performed in the loop thread; otherwise, if async handshake is disabled,
+    // this function performs the handshake and returns after the handshake finishes.
+    bool startHandshake() EXCLUDES(mLock);
+
     // Send a query on the provided SSL socket.  |query| contains
     // the body of a query, not including the ID header. This function will typically return before
     // the query is actually sent.  If this function fails, DnsTlsSocketObserver will be
@@ -77,10 +114,16 @@
     // On error, returns the errno.
     netdutils::Status tcpConnect() REQUIRES(mLock);
 
+    bssl::UniquePtr<SSL> prepareForSslConnect(int fd) REQUIRES(mLock);
+
     // Connect an SSL session on the provided socket.  If connection fails, closing the
     // socket remains the caller's responsibility.
     bssl::UniquePtr<SSL> sslConnect(int fd) REQUIRES(mLock);
 
+    // Connect an SSL session on the provided socket. This is an interruptible version
+    // which allows to terminate connection handshake any time.
+    bssl::UniquePtr<SSL> sslConnectV2(int fd) REQUIRES(mLock);
+
     // Disconnect the SSL session and close the socket.
     void sslDisconnect() REQUIRES(mLock);
 
@@ -94,6 +137,9 @@
     int sslRead(const netdutils::Slice buffer, bool wait) REQUIRES(mLock);
 
     bool sendQuery(const std::vector<uint8_t>& buf) REQUIRES(mLock);
+
+    // Read one DNS response. It can potentially block until reading the exact bytes of
+    // the response.
     bool readResponse() REQUIRES(mLock);
 
     // It is only used for DNS-OVER-TLS internal test.
@@ -108,6 +154,9 @@
     // This function sends a message to the loop thread by incrementing mEventFd.
     bool incrementEventFd(int64_t count) EXCLUDES(mLock);
 
+    // Transition the state from expected state |from| to new state |to|.
+    void transitionState(State from, State to) REQUIRES(mLock);
+
     // Queue of pending queries.  query() pushes items onto the queue and notifies
     // the loop thread by incrementing mEventFd.  loop() reads items off the queue.
     LockedQueue<std::vector<uint8_t>> mQueue;
@@ -119,8 +168,14 @@
     // EOF, we indicate a close request by setting the counter to a negative number.
     // This file descriptor is opened by initialize(), and closed implicitly after
     // destruction.
+    // Note that: data starts being read from the eventfd when the state is CONNECTED.
     base::unique_fd mEventFd;
 
+    // An eventfd used to listen to shutdown requests when the state is CONNECTING.
+    // TODO: let |mEventFd| exclusively handle query requests, and let |mShutdownEvent| exclusively
+    // handle shutdown requests.
+    base::unique_fd mShutdownEvent;
+
     // SSL Socket fields.
     bssl::UniquePtr<SSL_CTX> mSslCtx GUARDED_BY(mLock);
     base::unique_fd mSslFd GUARDED_BY(mLock);
@@ -131,6 +186,20 @@
     const DnsTlsServer mServer;
     IDnsTlsSocketObserver* _Nonnull const mObserver;
     DnsTlsSessionCache* _Nonnull const mCache;
+    State mState GUARDED_BY(mLock) = State::UNINITIALIZED;
+
+    // If true, defer the handshake to the loop thread; otherwise, run the handshake on caller's
+    // thread (the call to startHandshake()).
+    bool mAsyncHandshake GUARDED_BY(mLock) = false;
+
+    // The time to wait for the attempt on connecting to the server.
+    // Set the default value 127 seconds to be consistent with TCP connect timeout.
+    // (presume net.ipv4.tcp_syn_retries = 6)
+    static constexpr int kDotConnectTimeoutMs = 127 * 1000;
+    int mConnectTimeoutMs;
+
+    // For testing.
+    friend class DnsTlsSocketTest;
 };
 
 }  // end of namespace net
diff --git a/DnsTlsTransport.cpp b/DnsTlsTransport.cpp
index d0098c2..a2964cc 100644
--- a/DnsTlsTransport.cpp
+++ b/DnsTlsTransport.cpp
@@ -70,9 +70,14 @@
 void DnsTlsTransport::doConnect() {
     LOG(DEBUG) << "Constructing new socket";
     mSocket = mFactory->createDnsTlsSocket(mServer, mMark, this, &mCache);
+
+    bool success = true;
+    if (mSocket.get() == nullptr || !mSocket->startHandshake()) {
+        success = false;
+    }
     mConnectCounter++;
 
-    if (mSocket) {
+    if (success) {
         auto queries = mQueries.getAll();
         LOG(DEBUG) << "Initialization succeeded.  Reissuing " << queries.size() << " queries.";
         for(auto& q : queries) {
@@ -153,8 +158,8 @@
 // static
 // TODO: Use this function to preheat the session cache.
 // That may require moving it to DnsTlsDispatcher.
-bool DnsTlsTransport::validate(const DnsTlsServer& server, unsigned netid, uint32_t mark) {
-    LOG(DEBUG) << "Beginning validation on " << netid;
+bool DnsTlsTransport::validate(const DnsTlsServer& server, uint32_t mark) {
+    LOG(DEBUG) << "Beginning validation with mark " << std::hex << mark;
     // Generate "<random>-dnsotls-ds.metric.gstatic.com", which we will lookup through |ss| in
     // order to prove that it is actually a working DNS over TLS server.
     static const char kDnsSafeChars[] =
@@ -190,7 +195,7 @@
     DnsTlsTransport transport(server, mark, &factory);
     auto r = transport.query(netdutils::Slice(query, qlen)).get();
     if (r.code != Response::success) {
-        LOG(DEBUG) << "query failed";
+        LOG(WARNING) << "query failed";
         return false;
     }
 
@@ -207,7 +212,7 @@
     }
 
     const int ancount = (recvbuf[6] << 8) | recvbuf[7];
-    LOG(DEBUG) << netid << " answer count: " << ancount;
+    LOG(DEBUG) << "answer count: " << ancount;
 
     // TODO: Further validate the response contents (check for valid AAAA record, ...).
     // Note that currently, integration tests rely on this function accepting a
diff --git a/DnsTlsTransport.h b/DnsTlsTransport.h
index c0fbaef..7b7f81f 100644
--- a/DnsTlsTransport.h
+++ b/DnsTlsTransport.h
@@ -52,10 +52,10 @@
     // Given a |query|, this method sends it to the server and returns the result asynchronously.
     std::future<Result> query(const netdutils::Slice query) EXCLUDES(mLock);
 
-    // Check that a given TLS server is fully working on the specified netid.
+    // Check that a given TLS server is fully working with a specified mark.
     // This function is used in ResolverController to ensure that we don't enable DNS over TLS
     // on networks where it doesn't actually work.
-    static bool validate(const DnsTlsServer& server, unsigned netid, uint32_t mark);
+    static bool validate(const DnsTlsServer& server, uint32_t mark);
 
     int getConnectCounter() const EXCLUDES(mLock);
 
diff --git a/Experiments.h b/Experiments.h
index e267e50..bc03822 100644
--- a/Experiments.h
+++ b/Experiments.h
@@ -47,10 +47,12 @@
     mutable std::mutex mMutex;
     std::unordered_map<std::string_view, int> mFlagsMapInt GUARDED_BY(mMutex);
     // TODO: Migrate other experiment flags to here.
-    // (retry_count, retransmission_time_interval, dot_connect_timeout_ms)
+    // (retry_count, retransmission_time_interval)
     static constexpr const char* const kExperimentFlagKeyList[] = {
-            "keep_listening_udp", "parallel_lookup", "parallel_lookup_sleep_time",
-            "sort_nameservers"};
+            "keep_listening_udp", "parallel_lookup_release", "parallel_lookup_sleep_time",
+            "sort_nameservers",   "dot_async_handshake",     "dot_connect_timeout_ms",
+            "dot_maxtries",
+    };
     // This value is used in updateInternal as the default value if any flags can't be found.
     static constexpr int kFlagIntDefault = INT_MIN;
     // For testing.
diff --git a/IDnsTlsSocket.h b/IDnsTlsSocket.h
index 0f2800e..8d2be8a 100644
--- a/IDnsTlsSocket.h
+++ b/IDnsTlsSocket.h
@@ -40,6 +40,8 @@
     // notified that the socket is closed.
     // Note that a true return value indicates successful sending, not receipt of a response.
     virtual bool query(uint16_t id, const netdutils::Slice query) = 0;
+
+    virtual bool startHandshake() = 0;
 };
 
 }  // end of namespace net
diff --git a/LockedQueue.h b/LockedQueue.h
index 0481eda..8694f2d 100644
--- a/LockedQueue.h
+++ b/LockedQueue.h
@@ -46,6 +46,30 @@
     std::deque<T> mQueue GUARDED_BY(mLock);
 };
 
+template <typename T>
+class LockedRingBuffer {
+  public:
+    explicit LockedRingBuffer(size_t size) : mCapacity(size) {}
+
+    void push(T&& record) {
+        std::lock_guard guard(mLock);
+        mQueue.push_back(std::move(record));
+        if (mQueue.size() > mCapacity) {
+            mQueue.pop_front();
+        }
+    }
+
+    std::deque<T> copy() const {
+        std::lock_guard guard(mLock);
+        return mQueue;
+    }
+
+  private:
+    mutable std::mutex mLock;
+    const size_t mCapacity;
+    std::deque<T> mQueue GUARDED_BY(mLock);
+};
+
 }  // end of namespace net
 }  // end of namespace android
 
diff --git a/OperationLimiter.h b/OperationLimiter.h
new file mode 100644
index 0000000..1fa1bf2
--- /dev/null
+++ b/OperationLimiter.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef NETUTILS_OPERATIONLIMITER_H
+#define NETUTILS_OPERATIONLIMITER_H
+
+#include <mutex>
+#include <unordered_map>
+
+#include <android-base/logging.h>
+#include <android-base/thread_annotations.h>
+
+namespace android {
+namespace netdutils {
+
+// Tracks the number of operations in progress on behalf of a particular key or
+// ID, rejecting further attempts to start new operations after a configurable
+// limit has been reached.
+//
+// The intended usage pattern is:
+//     OperationLimiter<UserId> connections_per_user;
+//     ...
+//     int connectToSomeResource(int user) {
+//         if (!connections_per_user.start(user)) return TRY_AGAIN_LATER;
+//         // ...do expensive work here...
+//         connections_per_user.finish(user);
+//     }
+//
+// This class is thread-safe.
+template <typename KeyType>
+class OperationLimiter {
+  public:
+    OperationLimiter(int limitPerKey, int globalLimit = INT_MAX)
+        : mLimitPerKey(limitPerKey), mGlobalLimit(globalLimit) {}
+
+    ~OperationLimiter() {
+        DCHECK(mCounters.empty()) << "Destroying OperationLimiter with active operations";
+    }
+
+    // Returns false if |key| has reached the maximum number of concurrent operations,
+    // or if the global limit has been reached. Otherwise, increments the counter and returns true.
+    //
+    // Note: each successful start(key) must be matched by exactly one call to
+    // finish(key).
+    bool start(KeyType key) EXCLUDES(mMutex) {
+        std::lock_guard lock(mMutex);
+
+        if (mGlobalCounter >= mGlobalLimit) {
+            // Oh, no!
+            return false;
+        }
+
+        auto& cnt = mCounters[key];  // operator[] creates new entries as needed.
+        if (cnt >= mLimitPerKey) {
+            // Oh, no!
+            return false;
+        }
+
+        ++cnt;
+        ++mGlobalCounter;
+        return true;
+    }
+
+    // Decrements the number of operations in progress accounted to |key|.
+    // See usage notes on start().
+    void finish(KeyType key) EXCLUDES(mMutex) {
+        std::lock_guard lock(mMutex);
+
+        --mGlobalCounter;
+        if (mGlobalCounter < 0) {
+            LOG(FATAL_WITHOUT_ABORT) << "Global operations counter going negative, this is a bug.";
+            return;
+        }
+
+        auto it = mCounters.find(key);
+        if (it == mCounters.end()) {
+            LOG(FATAL_WITHOUT_ABORT) << "Decremented non-existent counter for key=" << key;
+            return;
+        }
+        auto& cnt = it->second;
+        --cnt;
+        if (cnt <= 0) {
+            // Cleanup counters once they drop down to zero.
+            mCounters.erase(it);
+        }
+    }
+
+  private:
+    // Protects access to the map below.
+    std::mutex mMutex;
+
+    // Tracks the number of outstanding queries by key.
+    std::unordered_map<KeyType, int> mCounters GUARDED_BY(mMutex);
+
+    int mGlobalCounter GUARDED_BY(mMutex) = 0;
+
+    // Maximum number of outstanding queries from a single key.
+    const int mLimitPerKey;
+
+    // Maximum number of outstanding queries, globally.
+    const int mGlobalLimit;
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif  // NETUTILS_OPERATIONLIMITER_H
diff --git a/OperationLimiterTest.cpp b/OperationLimiterTest.cpp
new file mode 100644
index 0000000..9d2dadb
--- /dev/null
+++ b/OperationLimiterTest.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 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 "OperationLimiter.h"
+
+#include <gtest/gtest-spi.h>
+
+namespace android {
+namespace netdutils {
+
+TEST(OperationLimiter, limits) {
+    OperationLimiter<int> limiter(3);
+
+    EXPECT_TRUE(limiter.start(42));
+    EXPECT_TRUE(limiter.start(42));
+    EXPECT_TRUE(limiter.start(42));
+
+    // Limit reached... calling any number of times should have no effect.
+    EXPECT_FALSE(limiter.start(42));
+    EXPECT_FALSE(limiter.start(42));
+    EXPECT_FALSE(limiter.start(42));
+
+    // Finishing a single operations is enough for starting a new one...
+    limiter.finish(42);
+    EXPECT_TRUE(limiter.start(42));
+
+    // ...but not two!
+    EXPECT_FALSE(limiter.start(42));
+
+    // Different ids should still have quota...
+    EXPECT_TRUE(limiter.start(666));
+    limiter.finish(666);
+
+    // Finish all pending operations
+    limiter.finish(42);
+    limiter.finish(42);
+    limiter.finish(42);
+}
+
+TEST(OperationLimiter, finishWithoutStart) {
+    OperationLimiter<int> limiter(1);
+
+    // Will output a LOG(FATAL_WITHOUT_ABORT), but we have no way to probe this.
+    limiter.finish(42);
+
+    // This will ensure that the finish() above didn't set a negative value.
+    EXPECT_TRUE(limiter.start(42));
+    EXPECT_FALSE(limiter.start(42));
+}
+
+TEST(OperationLimiter, destroyWithActiveOperations) {
+    // The death message doesn't seem to be captured on Android.
+    EXPECT_DEBUG_DEATH(
+            {
+                OperationLimiter<int> limiter(3);
+                limiter.start(42);
+            },
+            "" /* "active operations */);
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 27eac94..a1ccc2e 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,6 +1,8 @@
 [Builtin Hooks]
 clang_format = true
 commit_msg_test_field = false
+rustfmt = true
 
 [Builtin Hooks Options]
 clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+rustfmt = --config-path=rustfmt.toml
diff --git a/PrivateDnsCommon.h b/PrivateDnsCommon.h
new file mode 100644
index 0000000..229e97c
--- /dev/null
+++ b/PrivateDnsCommon.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace android::net {
+
+// Validation status of a private DNS server on a specific netId.
+enum class Validation : uint8_t {
+    in_process,
+    success,
+    success_but_expired,
+    fail,
+    unknown_server,
+    unknown_netid,
+};
+
+// The private DNS mode on a specific netId.
+enum class PrivateDnsMode : uint8_t {
+    OFF,
+    OPPORTUNISTIC,
+    STRICT,
+};
+
+constexpr const char* validationStatusToString(Validation value) {
+    switch (value) {
+        case Validation::in_process:
+            return "in_process";
+        case Validation::success:
+            return "success";
+        case Validation::success_but_expired:
+            return "success_but_expired";
+        case Validation::fail:
+            return "fail";
+        case Validation::unknown_server:
+            return "unknown_server";
+        case Validation::unknown_netid:
+            return "unknown_netid";
+        default:
+            return "unknown_status";
+    }
+}
+
+constexpr const char* getPrivateDnsModeString(PrivateDnsMode mode) {
+    switch (mode) {
+        case PrivateDnsMode::OFF:
+            return "OFF";
+        case PrivateDnsMode::OPPORTUNISTIC:
+            return "OPPORTUNISTIC";
+        case PrivateDnsMode::STRICT:
+            return "STRICT";
+    }
+}
+
+}  // namespace android::net
diff --git a/PrivateDnsConfiguration.cpp b/PrivateDnsConfiguration.cpp
index 7d9e82a..8f3b836 100644
--- a/PrivateDnsConfiguration.cpp
+++ b/PrivateDnsConfiguration.cpp
@@ -18,9 +18,9 @@
 
 #include "PrivateDnsConfiguration.h"
 
+#include <android-base/format.h>
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
-#include <netdb.h>
 #include <netdutils/ThreadUtil.h>
 #include <sys/socket.h>
 
@@ -28,7 +28,6 @@
 #include "ResolverEventReporter.h"
 #include "netd_resolv/resolv.h"
 #include "netdutils/BackoffSequence.h"
-#include "resolv_cache.h"
 #include "util.h"
 
 using android::base::StringPrintf;
@@ -38,22 +37,6 @@
 namespace android {
 namespace net {
 
-namespace {
-
-milliseconds getExperimentTimeout(const std::string& flagName, const milliseconds defaultValue) {
-    int val = getExperimentFlagInt(flagName, defaultValue.count());
-    return milliseconds((val < 1000) ? 1000 : val);
-}
-
-}  // namespace
-
-std::string addrToString(const sockaddr_storage* addr) {
-    char out[INET6_ADDRSTRLEN] = {0};
-    getnameinfo((const sockaddr*) addr, sizeof(sockaddr_storage), out, INET6_ADDRSTRLEN, nullptr, 0,
-                NI_NUMERICHOST);
-    return std::string(out);
-}
-
 bool parseServer(const char* server, sockaddr_storage* parsed) {
     addrinfo hints = {
             .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV,
@@ -79,7 +62,7 @@
                << ", " << servers.size() << ", " << name << ")";
 
     // Parse the list of servers that has been passed in
-    std::set<DnsTlsServer> tlsServers;
+    PrivateDnsTracker tmp;
     for (const auto& s : servers) {
         sockaddr_storage parsed;
         if (!parseServer(s.c_str(), &parsed)) {
@@ -88,59 +71,48 @@
         DnsTlsServer server(parsed);
         server.name = name;
         server.certificate = caCert;
-        server.connectTimeout =
-                getExperimentTimeout("dot_connect_timeout_ms", DnsTlsServer::kDotConnectTimeoutMs);
-        tlsServers.insert(server);
-        LOG(DEBUG) << "Set DoT connect timeout " << server.connectTimeout.count() << "ms for " << s;
+        server.mark = mark;
+        tmp[ServerIdentity(server)] = server;
     }
 
     std::lock_guard guard(mPrivateDnsLock);
     if (!name.empty()) {
         mPrivateDnsModes[netId] = PrivateDnsMode::STRICT;
-    } else if (!tlsServers.empty()) {
+    } else if (!tmp.empty()) {
         mPrivateDnsModes[netId] = PrivateDnsMode::OPPORTUNISTIC;
     } else {
         mPrivateDnsModes[netId] = PrivateDnsMode::OFF;
         mPrivateDnsTransports.erase(netId);
-        resolv_stats_set_servers_for_dot(netId, {});
-        mPrivateDnsValidateThreads.erase(netId);
-        // TODO: As mPrivateDnsValidateThreads is reset, validation threads which haven't yet
-        // finished are considered outdated. Consider signaling the outdated validation threads to
-        // stop them from updating the state of PrivateDnsConfiguration (possibly disallow them to
-        // report onPrivateDnsValidationEvent).
+        // TODO: signal validation threads to stop.
         return 0;
     }
 
     // Create the tracker if it was not present
-    auto netPair = mPrivateDnsTransports.find(netId);
-    if (netPair == mPrivateDnsTransports.end()) {
-        // No TLS tracker yet for this netId.
-        bool added;
-        std::tie(netPair, added) = mPrivateDnsTransports.emplace(netId, PrivateDnsTracker());
-        if (!added) {
-            LOG(ERROR) << "Memory error while recording private DNS for netId " << netId;
-            return -ENOMEM;
-        }
-    }
-    auto& tracker = netPair->second;
+    auto& tracker = mPrivateDnsTransports[netId];
 
-    // Remove any servers from the tracker that are not in |servers| exactly.
-    for (auto it = tracker.begin(); it != tracker.end();) {
-        if (tlsServers.count(it->first) == 0) {
-            it = tracker.erase(it);
-        } else {
-            ++it;
+    // Add the servers if not contained in tracker.
+    for (const auto& [identity, server] : tmp) {
+        if (tracker.find(identity) == tracker.end()) {
+            tracker[identity] = server;
         }
     }
 
-    // Add any new or changed servers to the tracker, and initiate async checks for them.
-    for (const auto& server : tlsServers) {
-        if (needsValidation(tracker, server)) {
-            validatePrivateDnsProvider(server, tracker, netId, mark);
+    for (auto& [identity, server] : tracker) {
+        const bool active = tmp.find(identity) != tmp.end();
+        server.setActive(active);
+
+        // For simplicity, deem the validation result of inactive servers as unreliable.
+        if (!server.active() && server.validationState() == Validation::success) {
+            updateServerState(identity, Validation::success_but_expired, netId);
+        }
+
+        if (needsValidation(server)) {
+            updateServerState(identity, Validation::in_process, netId);
+            startValidation(server, netId);
         }
     }
 
-    return resolv_stats_set_servers_for_dot(netId, servers);
+    return 0;
 }
 
 PrivateDnsStatus PrivateDnsConfiguration::getStatus(unsigned netId) {
@@ -153,8 +125,10 @@
 
     const auto netPair = mPrivateDnsTransports.find(netId);
     if (netPair != mPrivateDnsTransports.end()) {
-        for (const auto& serverPair : netPair->second) {
-            status.serversMap.emplace(serverPair.first, serverPair.second);
+        for (const auto& [_, server] : netPair->second) {
+            if (server.active()) {
+                status.serversMap.emplace(server, server.validationState());
+            }
         }
     }
 
@@ -166,22 +140,43 @@
     std::lock_guard guard(mPrivateDnsLock);
     mPrivateDnsModes.erase(netId);
     mPrivateDnsTransports.erase(netId);
-    mPrivateDnsValidateThreads.erase(netId);
 }
 
-void PrivateDnsConfiguration::validatePrivateDnsProvider(const DnsTlsServer& server,
-                                                         PrivateDnsTracker& tracker, unsigned netId,
-                                                         uint32_t mark) REQUIRES(mPrivateDnsLock) {
-    tracker[server] = Validation::in_process;
-    LOG(DEBUG) << "Server " << addrToString(&server.ss) << " marked as in_process on netId "
-               << netId << ". Tracker now has size " << tracker.size();
-    // This judge must be after "tracker[server] = Validation::in_process;"
-    if (!needValidateThread(server, netId)) {
-        return;
+bool PrivateDnsConfiguration::requestValidation(unsigned netId, const DnsTlsServer& server,
+                                                uint32_t mark) {
+    std::lock_guard guard(mPrivateDnsLock);
+    auto netPair = mPrivateDnsTransports.find(netId);
+    if (netPair == mPrivateDnsTransports.end()) {
+        return false;
     }
 
+    auto& tracker = netPair->second;
+    const ServerIdentity identity = ServerIdentity(server);
+    auto it = tracker.find(identity);
+    if (it == tracker.end()) {
+        return false;
+    }
+
+    const DnsTlsServer& target = it->second;
+
+    if (!target.active()) return false;
+
+    if (target.validationState() != Validation::success) return false;
+
+    // Don't run the validation if |mark| (from android_net_context.dns_mark) is different.
+    // This is to protect validation from running on unexpected marks.
+    // Validation should be associated with a mark gotten by system permission.
+    if (target.mark != mark) return false;
+
+    updateServerState(identity, Validation::in_process, netId);
+    startValidation(target, netId);
+    return true;
+}
+
+void PrivateDnsConfiguration::startValidation(const DnsTlsServer& server, unsigned netId)
+        REQUIRES(mPrivateDnsLock) {
     // Note that capturing |server| and |netId| in this lambda create copies.
-    std::thread validate_thread([this, server, netId, mark] {
+    std::thread validate_thread([this, server, netId] {
         setThreadName(StringPrintf("TlsVerify_%u", netId).c_str());
 
         // cat /proc/sys/net/ipv4/tcp_syn_retries yields "6".
@@ -205,10 +200,11 @@
         while (true) {
             // ::validate() is a blocking call that performs network operations.
             // It can take milliseconds to minutes, up to the SYN retry limit.
-            LOG(WARNING) << "Validating DnsTlsServer on netId " << netId;
-            const bool success = DnsTlsTransport::validate(server, netId, mark);
-            LOG(DEBUG) << "validateDnsTlsServer returned " << success << " for "
-                       << addrToString(&server.ss);
+            LOG(WARNING) << "Validating DnsTlsServer " << server.toIpString() << " with mark 0x"
+                         << std::hex << server.mark;
+            const bool success = DnsTlsTransport::validate(server, server.mark);
+            LOG(WARNING) << "validateDnsTlsServer returned " << success << " for "
+                         << server.toIpString();
 
             const bool needs_reeval = this->recordPrivateDnsValidation(server, netId, success);
             if (!needs_reeval) {
@@ -216,12 +212,12 @@
             }
 
             if (backoff.hasNextTimeout()) {
+                // TODO: make the thread able to receive signals to shutdown early.
                 std::this_thread::sleep_for(backoff.getNextTimeout());
             } else {
                 break;
             }
         }
-        this->cleanValidateThreadTracker(server, netId);
     });
     validate_thread.detach();
 }
@@ -230,18 +226,21 @@
                                                          bool success) {
     constexpr bool NEEDS_REEVALUATION = true;
     constexpr bool DONT_REEVALUATE = false;
+    const ServerIdentity identity = ServerIdentity(server);
 
     std::lock_guard guard(mPrivateDnsLock);
 
     auto netPair = mPrivateDnsTransports.find(netId);
     if (netPair == mPrivateDnsTransports.end()) {
         LOG(WARNING) << "netId " << netId << " was erased during private DNS validation";
+        notifyValidationStateUpdate(identity.ip.toString(), Validation::fail, netId);
         return DONT_REEVALUATE;
     }
 
     const auto mode = mPrivateDnsModes.find(netId);
     if (mode == mPrivateDnsModes.end()) {
         LOG(WARNING) << "netId " << netId << " has no private DNS validation mode";
+        notifyValidationStateUpdate(identity.ip.toString(), Validation::fail, netId);
         return DONT_REEVALUATE;
     }
     const bool modeDoesReevaluation = (mode->second == PrivateDnsMode::STRICT);
@@ -250,104 +249,115 @@
             (success || !modeDoesReevaluation) ? DONT_REEVALUATE : NEEDS_REEVALUATION;
 
     auto& tracker = netPair->second;
-    auto serverPair = tracker.find(server);
+    auto serverPair = tracker.find(identity);
     if (serverPair == tracker.end()) {
-        // TODO: Consider not adding this server to the tracker since this server is not expected
-        // to be one of the private DNS servers for this network now. This could prevent this
-        // server from being included when dumping status.
-        LOG(WARNING) << "Server " << addrToString(&server.ss)
+        LOG(WARNING) << "Server " << server.toIpString()
                      << " was removed during private DNS validation";
         success = false;
         reevaluationStatus = DONT_REEVALUATE;
-    } else if (!(serverPair->first == server)) {
+    } else if (!(serverPair->second == server)) {
         // TODO: It doesn't seem correct to overwrite the tracker entry for
         // |server| down below in this circumstance... Fix this.
-        LOG(WARNING) << "Server " << addrToString(&server.ss)
+        LOG(WARNING) << "Server " << server.toIpString()
                      << " was changed during private DNS validation";
         success = false;
         reevaluationStatus = DONT_REEVALUATE;
+    } else if (!serverPair->second.active()) {
+        LOG(WARNING) << "Server " << server.toIpString() << " was removed from the configuration";
+        success = false;
+        reevaluationStatus = DONT_REEVALUATE;
     }
 
     // Send a validation event to NetdEventListenerService.
     const auto& listeners = ResolverEventReporter::getInstance().getListeners();
     if (listeners.size() != 0) {
         for (const auto& it : listeners) {
-            it->onPrivateDnsValidationEvent(netId, addrToString(&server.ss), server.name, success);
+            it->onPrivateDnsValidationEvent(netId, server.toIpString(), server.name, success);
         }
         LOG(DEBUG) << "Sent validation " << (success ? "success" : "failure") << " event on netId "
-                   << netId << " for " << addrToString(&server.ss) << " with hostname {"
-                   << server.name << "}";
+                   << netId << " for " << server.toIpString() << " with hostname {" << server.name
+                   << "}";
     } else {
         LOG(ERROR)
                 << "Validation event not sent since no INetdEventListener receiver is available.";
     }
 
     if (success) {
-        tracker[server] = Validation::success;
+        updateServerState(identity, Validation::success, netId);
     } else {
         // Validation failure is expected if a user is on a captive portal.
         // TODO: Trigger a second validation attempt after captive portal login
         // succeeds.
-        tracker[server] = (reevaluationStatus == NEEDS_REEVALUATION) ? Validation::in_process
-                                                                     : Validation::fail;
+        const auto result = (reevaluationStatus == NEEDS_REEVALUATION) ? Validation::in_process
+                                                                       : Validation::fail;
+        updateServerState(identity, result, netId);
     }
     LOG(WARNING) << "Validation " << (success ? "success" : "failed");
 
     return reevaluationStatus;
 }
 
-bool PrivateDnsConfiguration::needValidateThread(const DnsTlsServer& server, unsigned netId)
-        REQUIRES(mPrivateDnsLock) {
-    // Create the thread tracker if it was not present
-    auto threadPair = mPrivateDnsValidateThreads.find(netId);
-    if (threadPair == mPrivateDnsValidateThreads.end()) {
-        // No thread tracker yet for this netId.
-        bool added;
-        std::tie(threadPair, added) = mPrivateDnsValidateThreads.emplace(netId, ThreadTracker());
-        if (!added) {
-            LOG(ERROR) << "Memory error while needValidateThread for netId " << netId;
-            return true;
-        }
+void PrivateDnsConfiguration::updateServerState(const ServerIdentity& identity, Validation state,
+                                                uint32_t netId) {
+    auto netPair = mPrivateDnsTransports.find(netId);
+    if (netPair == mPrivateDnsTransports.end()) {
+        notifyValidationStateUpdate(identity.ip.toString(), Validation::fail, netId);
+        return;
     }
-    auto& threadTracker = threadPair->second;
-    if (threadTracker.count(server)) {
-        LOG(DEBUG) << "Server " << addrToString(&(server.ss))
-                   << " validate thread is already running. Thread tracker now has size "
-                   << threadTracker.size();
-        return false;
-    } else {
-        threadTracker.insert(server);
-        LOG(DEBUG) << "Server " << addrToString(&(server.ss))
-                   << " validate thread is not running. Thread tracker now has size "
-                   << threadTracker.size();
-        return true;
+
+    auto& tracker = netPair->second;
+    if (tracker.find(identity) == tracker.end()) {
+        notifyValidationStateUpdate(identity.ip.toString(), Validation::fail, netId);
+        return;
+    }
+
+    tracker[identity].setValidationState(state);
+    notifyValidationStateUpdate(identity.ip.toString(), state, netId);
+
+    RecordEntry record(netId, identity, state);
+    mPrivateDnsLog.push(std::move(record));
+}
+
+bool PrivateDnsConfiguration::needsValidation(const DnsTlsServer& server) {
+    // The server is not expected to be used on the network.
+    if (!server.active()) return false;
+
+    // The server is newly added.
+    if (server.validationState() == Validation::unknown_server) return true;
+
+    // The server has failed at least one validation attempt. Give it another try.
+    if (server.validationState() == Validation::fail) return true;
+
+    // The previous validation result might be unreliable.
+    if (server.validationState() == Validation::success_but_expired) return true;
+
+    return false;
+}
+
+void PrivateDnsConfiguration::setObserver(PrivateDnsValidationObserver* observer) {
+    std::lock_guard guard(mPrivateDnsLock);
+    mObserver = observer;
+}
+
+void PrivateDnsConfiguration::notifyValidationStateUpdate(const std::string& serverIp,
+                                                          Validation validation,
+                                                          uint32_t netId) const {
+    if (mObserver) {
+        mObserver->onValidationStateUpdate(serverIp, validation, netId);
     }
 }
 
-void PrivateDnsConfiguration::cleanValidateThreadTracker(const DnsTlsServer& server,
-                                                         unsigned netId) {
-    std::lock_guard<std::mutex> guard(mPrivateDnsLock);
-    LOG(DEBUG) << "cleanValidateThreadTracker Server " << addrToString(&(server.ss))
-               << " validate thread is stopped.";
+void PrivateDnsConfiguration::dump(netdutils::DumpWriter& dw) const {
+    dw.println("PrivateDnsLog:");
+    netdutils::ScopedIndent indentStats(dw);
 
-    auto threadPair = mPrivateDnsValidateThreads.find(netId);
-    if (threadPair != mPrivateDnsValidateThreads.end()) {
-        auto& threadTracker = threadPair->second;
-        threadTracker.erase(server);
-        LOG(DEBUG) << "Server " << addrToString(&(server.ss))
-                   << " validate thread is stopped. Thread tracker now has size "
-                   << threadTracker.size();
+    for (const auto& record : mPrivateDnsLog.copy()) {
+        dw.println(fmt::format("{} - netId={} PrivateDns={{{}/{}}} state={}",
+                               timestampToString(record.timestamp), record.netId,
+                               record.serverIdentity.ip.toString(), record.serverIdentity.name,
+                               validationStatusToString(record.state)));
     }
-}
-
-// Start validation for newly added servers as well as any servers that have
-// landed in Validation::fail state. Note that servers that have failed
-// multiple validation attempts but for which there is still a validating
-// thread running are marked as being Validation::in_process.
-bool PrivateDnsConfiguration::needsValidation(const PrivateDnsTracker& tracker,
-                                              const DnsTlsServer& server) {
-    const auto& iter = tracker.find(server);
-    return (iter == tracker.end()) || (iter->second == Validation::fail);
+    dw.blankline();
 }
 
 }  // namespace net
diff --git a/PrivateDnsConfiguration.h b/PrivateDnsConfiguration.h
index 31fb546..07c6f2e 100644
--- a/PrivateDnsConfiguration.h
+++ b/PrivateDnsConfiguration.h
@@ -22,20 +22,20 @@
 #include <vector>
 
 #include <android-base/thread_annotations.h>
+#include <netdutils/DumpWriter.h>
+#include <netdutils/InternetAddresses.h>
 
 #include "DnsTlsServer.h"
+#include "LockedQueue.h"
+#include "PrivateDnsValidationObserver.h"
 
 namespace android {
 namespace net {
 
-// The DNS over TLS mode on a specific netId.
-enum class PrivateDnsMode : uint8_t { OFF, OPPORTUNISTIC, STRICT };
-
-// Validation status of a DNS over TLS server (on a specific netId).
-enum class Validation : uint8_t { in_process, success, fail, unknown_server, unknown_netid };
-
 struct PrivateDnsStatus {
     PrivateDnsMode mode;
+
+    // TODO: change the type to std::vector<DnsTlsServer>.
     std::map<DnsTlsServer, Validation, AddressComparator> serversMap;
 
     std::list<DnsTlsServer> validatedServers() const {
@@ -65,32 +65,80 @@
 
     void clear(unsigned netId) EXCLUDES(mPrivateDnsLock);
 
+    // Request |server| to be revalidated on a connection tagged with |mark|.
+    // Return true if the request is accepted; otherwise, return false.
+    bool requestValidation(unsigned netId, const DnsTlsServer& server, uint32_t mark)
+            EXCLUDES(mPrivateDnsLock);
+
+    struct ServerIdentity {
+        const netdutils::IPAddress ip;
+        const std::string name;
+        const int protocol;
+
+        explicit ServerIdentity(const DnsTlsServer& server)
+            : ip(netdutils::IPSockAddr::toIPSockAddr(server.ss).ip()),
+              name(server.name),
+              protocol(server.protocol) {}
+
+        bool operator<(const ServerIdentity& other) const {
+            return std::tie(ip, name, protocol) < std::tie(other.ip, other.name, other.protocol);
+        }
+        bool operator==(const ServerIdentity& other) const {
+            return std::tie(ip, name, protocol) == std::tie(other.ip, other.name, other.protocol);
+        }
+    };
+
+    void setObserver(PrivateDnsValidationObserver* observer);
+
+    void dump(netdutils::DumpWriter& dw) const;
+
   private:
-    typedef std::map<DnsTlsServer, Validation, AddressComparator> PrivateDnsTracker;
+    typedef std::map<ServerIdentity, DnsTlsServer> PrivateDnsTracker;
     typedef std::set<DnsTlsServer, AddressComparator> ThreadTracker;
 
     PrivateDnsConfiguration() = default;
 
-    void validatePrivateDnsProvider(const DnsTlsServer& server, PrivateDnsTracker& tracker,
-                                    unsigned netId, uint32_t mark) REQUIRES(mPrivateDnsLock);
+    void startValidation(const DnsTlsServer& server, unsigned netId) REQUIRES(mPrivateDnsLock);
 
-    bool recordPrivateDnsValidation(const DnsTlsServer& server, unsigned netId, bool success);
+    bool recordPrivateDnsValidation(const DnsTlsServer& server, unsigned netId, bool success)
+            EXCLUDES(mPrivateDnsLock);
 
-    bool needValidateThread(const DnsTlsServer& server, unsigned netId) REQUIRES(mPrivateDnsLock);
-    void cleanValidateThreadTracker(const DnsTlsServer& server, unsigned netId);
-
-    // Start validation for newly added servers as well as any servers that have
-    // landed in Validation::fail state. Note that servers that have failed
+    // Decide if a validation for |server| is needed. Note that servers that have failed
     // multiple validation attempts but for which there is still a validating
     // thread running are marked as being Validation::in_process.
-    bool needsValidation(const PrivateDnsTracker& tracker, const DnsTlsServer& server);
+    bool needsValidation(const DnsTlsServer& server) REQUIRES(mPrivateDnsLock);
+
+    void updateServerState(const ServerIdentity& identity, Validation state, uint32_t netId)
+            REQUIRES(mPrivateDnsLock);
 
     std::mutex mPrivateDnsLock;
     std::map<unsigned, PrivateDnsMode> mPrivateDnsModes GUARDED_BY(mPrivateDnsLock);
-    // Structure for tracking the validation status of servers on a specific netId.
-    // Using the AddressComparator ensures at most one entry per IP address.
+
+    // Contains all servers for a network, along with their current validation status.
+    // In case a server is removed due to a configuration change, it remains in this map,
+    // but is marked inactive.
+    // Any pending validation threads will continue running because we have no way to cancel them.
     std::map<unsigned, PrivateDnsTracker> mPrivateDnsTransports GUARDED_BY(mPrivateDnsLock);
-    std::map<unsigned, ThreadTracker> mPrivateDnsValidateThreads GUARDED_BY(mPrivateDnsLock);
+
+    void notifyValidationStateUpdate(const std::string& serverIp, Validation validation,
+                                     uint32_t netId) const REQUIRES(mPrivateDnsLock);
+
+    // TODO: fix the reentrancy problem.
+    PrivateDnsValidationObserver* mObserver GUARDED_BY(mPrivateDnsLock);
+
+    friend class PrivateDnsConfigurationTest;
+
+    struct RecordEntry {
+        RecordEntry(uint32_t netId, const ServerIdentity& identity, Validation state)
+            : netId(netId), serverIdentity(identity), state(state) {}
+
+        const uint32_t netId;
+        const ServerIdentity serverIdentity;
+        const Validation state;
+        const std::chrono::system_clock::time_point timestamp = std::chrono::system_clock::now();
+    };
+
+    LockedRingBuffer<RecordEntry> mPrivateDnsLog{100};
 };
 
 }  // namespace net
diff --git a/PrivateDnsConfigurationTest.cpp b/PrivateDnsConfigurationTest.cpp
new file mode 100644
index 0000000..8da3efb
--- /dev/null
+++ b/PrivateDnsConfigurationTest.cpp
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2020 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "PrivateDnsConfiguration.h"
+#include "tests/dns_responder/dns_responder.h"
+#include "tests/dns_responder/dns_tls_frontend.h"
+#include "tests/resolv_test_utils.h"
+
+namespace android::net {
+
+using namespace std::chrono_literals;
+
+class PrivateDnsConfigurationTest : public ::testing::Test {
+  public:
+    static void SetUpTestSuite() {
+        // stopServer() will be called in their destructor.
+        ASSERT_TRUE(tls1.startServer());
+        ASSERT_TRUE(tls2.startServer());
+        ASSERT_TRUE(backend.startServer());
+    }
+
+    void SetUp() {
+        mPdc.setObserver(&mObserver);
+
+        // The default and sole action when the observer is notified of onValidationStateUpdate.
+        // Don't override the action. In other words, don't use WillOnce() or WillRepeatedly()
+        // when mObserver.onValidationStateUpdate is expected to be called, like:
+        //
+        //   EXPECT_CALL(mObserver, onValidationStateUpdate).WillOnce(Return());
+        //
+        // This is to ensure that tests can monitor how many validation threads are running. Tests
+        // must wait until every validation thread finishes.
+        ON_CALL(mObserver, onValidationStateUpdate)
+                .WillByDefault([&](const std::string& server, Validation validation, uint32_t) {
+                    if (validation == Validation::in_process) {
+                        mObserver.runningThreads++;
+                    } else if (validation == Validation::success ||
+                               validation == Validation::fail) {
+                        mObserver.runningThreads--;
+                    }
+                    std::lock_guard guard(mObserver.lock);
+                    mObserver.serverStateMap[server] = validation;
+                });
+    }
+
+  protected:
+    class MockObserver : public PrivateDnsValidationObserver {
+      public:
+        MOCK_METHOD(void, onValidationStateUpdate,
+                    (const std::string& serverIp, Validation validation, uint32_t netId),
+                    (override));
+
+        std::map<std::string, Validation> getServerStateMap() const {
+            std::lock_guard guard(lock);
+            return serverStateMap;
+        }
+
+        void removeFromServerStateMap(const std::string& server) {
+            std::lock_guard guard(lock);
+            if (const auto it = serverStateMap.find(server); it != serverStateMap.end())
+                serverStateMap.erase(it);
+        }
+
+        // The current number of validation threads running.
+        std::atomic<int> runningThreads = 0;
+
+        mutable std::mutex lock;
+        std::map<std::string, Validation> serverStateMap GUARDED_BY(lock);
+    };
+
+    void expectPrivateDnsStatus(PrivateDnsMode mode) {
+        const PrivateDnsStatus status = mPdc.getStatus(kNetId);
+        EXPECT_EQ(status.mode, mode);
+
+        std::map<std::string, Validation> serverStateMap;
+        for (const auto& [server, validation] : status.serversMap) {
+            serverStateMap[ToString(&server.ss)] = validation;
+        }
+        EXPECT_EQ(serverStateMap, mObserver.getServerStateMap());
+    }
+
+    static constexpr uint32_t kNetId = 30;
+    static constexpr uint32_t kMark = 30;
+    static constexpr char kBackend[] = "127.0.2.1";
+    static constexpr char kServer1[] = "127.0.2.2";
+    static constexpr char kServer2[] = "127.0.2.3";
+
+    MockObserver mObserver;
+    PrivateDnsConfiguration mPdc;
+
+    // TODO: Because incorrect CAs result in validation failed in strict mode, have
+    // PrivateDnsConfiguration run mocked code rather than DnsTlsTransport::validate().
+    inline static test::DnsTlsFrontend tls1{kServer1, "853", kBackend, "53"};
+    inline static test::DnsTlsFrontend tls2{kServer2, "853", kBackend, "53"};
+    inline static test::DNSResponder backend{kBackend, "53"};
+};
+
+TEST_F(PrivateDnsConfigurationTest, ValidationSuccess) {
+    testing::InSequence seq;
+    EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
+    EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
+
+    EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
+    expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+
+    ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
+}
+
+TEST_F(PrivateDnsConfigurationTest, ValidationFail_Opportunistic) {
+    ASSERT_TRUE(backend.stopServer());
+
+    testing::InSequence seq;
+    EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
+    EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::fail, kNetId));
+
+    EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
+    expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+
+    // Strictly wait for all of the validation finish; otherwise, the test can crash somehow.
+    ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
+    ASSERT_TRUE(backend.startServer());
+}
+
+TEST_F(PrivateDnsConfigurationTest, ValidationBlock) {
+    backend.setDeferredResp(true);
+
+    // onValidationStateUpdate() is called in sequence.
+    {
+        testing::InSequence seq;
+        EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
+        EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
+        ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 1; }));
+        expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+
+        EXPECT_CALL(mObserver, onValidationStateUpdate(kServer2, Validation::in_process, kNetId));
+        EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer2}, {}, {}), 0);
+        ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 2; }));
+        mObserver.removeFromServerStateMap(kServer1);
+        expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+
+        // No duplicate validation as long as not in OFF mode; otherwise, an unexpected
+        // onValidationStateUpdate() will be caught.
+        EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
+        EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1, kServer2}, {}, {}), 0);
+        EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer2}, {}, {}), 0);
+        expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+
+        // The status keeps unchanged if pass invalid arguments.
+        EXPECT_EQ(mPdc.set(kNetId, kMark, {"invalid_addr"}, {}, {}), -EINVAL);
+        expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+    }
+
+    // The update for |kServer1| will be Validation::fail because |kServer1| is not an expected
+    // server for the network.
+    EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::fail, kNetId));
+    EXPECT_CALL(mObserver, onValidationStateUpdate(kServer2, Validation::success, kNetId));
+    backend.setDeferredResp(false);
+
+    ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
+
+    // kServer1 is not a present server and thus should not be available from
+    // PrivateDnsConfiguration::getStatus().
+    mObserver.removeFromServerStateMap(kServer1);
+
+    expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+}
+
+TEST_F(PrivateDnsConfigurationTest, Validation_NetworkDestroyedOrOffMode) {
+    for (const std::string_view config : {"OFF", "NETWORK_DESTROYED"}) {
+        SCOPED_TRACE(config);
+        backend.setDeferredResp(true);
+
+        testing::InSequence seq;
+        EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
+        EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
+        ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 1; }));
+        expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+
+        if (config == "OFF") {
+            EXPECT_EQ(mPdc.set(kNetId, kMark, {}, {}, {}), 0);
+        } else if (config == "NETWORK_DESTROYED") {
+            mPdc.clear(kNetId);
+        }
+
+        EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::fail, kNetId));
+        backend.setDeferredResp(false);
+
+        ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
+        mObserver.removeFromServerStateMap(kServer1);
+        expectPrivateDnsStatus(PrivateDnsMode::OFF);
+    }
+}
+
+TEST_F(PrivateDnsConfigurationTest, NoValidation) {
+    // If onValidationStateUpdate() is called, the test will fail with uninteresting mock
+    // function calls in the end of the test.
+
+    const auto expectStatus = [&]() {
+        const PrivateDnsStatus status = mPdc.getStatus(kNetId);
+        EXPECT_EQ(status.mode, PrivateDnsMode::OFF);
+        EXPECT_THAT(status.serversMap, testing::IsEmpty());
+    };
+
+    EXPECT_EQ(mPdc.set(kNetId, kMark, {"invalid_addr"}, {}, {}), -EINVAL);
+    expectStatus();
+
+    EXPECT_EQ(mPdc.set(kNetId, kMark, {}, {}, {}), 0);
+    expectStatus();
+}
+
+TEST_F(PrivateDnsConfigurationTest, ServerIdentity_Comparison) {
+    using ServerIdentity = PrivateDnsConfiguration::ServerIdentity;
+
+    DnsTlsServer server(netdutils::IPSockAddr::toIPSockAddr("127.0.0.1", 853));
+    server.name = "dns.example.com";
+    server.protocol = 1;
+
+    // Different IP address (port is ignored).
+    DnsTlsServer other = server;
+    EXPECT_EQ(ServerIdentity(server), ServerIdentity(other));
+    other.ss = netdutils::IPSockAddr::toIPSockAddr("127.0.0.1", 5353);
+    EXPECT_EQ(ServerIdentity(server), ServerIdentity(other));
+    other.ss = netdutils::IPSockAddr::toIPSockAddr("127.0.0.2", 853);
+    EXPECT_NE(ServerIdentity(server), ServerIdentity(other));
+
+    // Different provider hostname.
+    other = server;
+    EXPECT_EQ(ServerIdentity(server), ServerIdentity(other));
+    other.name = "other.example.com";
+    EXPECT_NE(ServerIdentity(server), ServerIdentity(other));
+    other.name = "";
+    EXPECT_NE(ServerIdentity(server), ServerIdentity(other));
+
+    // Different protocol.
+    other = server;
+    EXPECT_EQ(ServerIdentity(server), ServerIdentity(other));
+    other.protocol++;
+    EXPECT_NE(ServerIdentity(server), ServerIdentity(other));
+}
+
+TEST_F(PrivateDnsConfigurationTest, RequestValidation) {
+    const DnsTlsServer server(netdutils::IPSockAddr::toIPSockAddr(kServer1, 853));
+
+    testing::InSequence seq;
+
+    for (const std::string_view config : {"SUCCESS", "IN_PROGRESS", "FAIL"}) {
+        SCOPED_TRACE(config);
+
+        EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
+        if (config == "SUCCESS") {
+            EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
+        } else if (config == "IN_PROGRESS") {
+            backend.setDeferredResp(true);
+        } else {
+            // config = "FAIL"
+            ASSERT_TRUE(backend.stopServer());
+            EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::fail, kNetId));
+        }
+        EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
+        expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+
+        // Wait until the validation state is transitioned.
+        const int runningThreads = (config == "IN_PROGRESS") ? 1 : 0;
+        ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == runningThreads; }));
+
+        bool requestAccepted = false;
+        if (config == "SUCCESS") {
+            EXPECT_CALL(mObserver,
+                        onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
+            EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
+            requestAccepted = true;
+        } else if (config == "IN_PROGRESS") {
+            EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
+        }
+
+        EXPECT_EQ(mPdc.requestValidation(kNetId, server, kMark), requestAccepted);
+
+        // Resending the same request or requesting nonexistent servers are denied.
+        EXPECT_FALSE(mPdc.requestValidation(kNetId, server, kMark));
+        EXPECT_FALSE(mPdc.requestValidation(kNetId, server, kMark + 1));
+        EXPECT_FALSE(mPdc.requestValidation(kNetId + 1, server, kMark));
+
+        // Reset the test state.
+        backend.setDeferredResp(false);
+        backend.startServer();
+        ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
+        mPdc.clear(kNetId);
+    }
+}
+
+// TODO: add ValidationFail_Strict test.
+
+}  // namespace android::net
diff --git a/PrivateDnsValidationObserver.h b/PrivateDnsValidationObserver.h
new file mode 100644
index 0000000..b0cd5e2
--- /dev/null
+++ b/PrivateDnsValidationObserver.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "PrivateDnsCommon.h"
+
+namespace android::net {
+
+// The observer is notified of onValidationStateUpdate 1) when a validation is
+// about to begin or 2) when a validation finishes. If a validation finishes when in OFF mode
+// or when the network has been destroyed, |validation| will be Validation::fail.
+// WARNING: The Observer is notified while the lock is being held. Be careful not to call
+// any method of PrivateDnsConfiguration from the observer to cause reentrancy problem.
+class PrivateDnsValidationObserver {
+  public:
+    virtual ~PrivateDnsValidationObserver(){};
+    virtual void onValidationStateUpdate(const std::string& serverIp, Validation validation,
+                                         uint32_t netId) = 0;
+};
+
+}  // namespace android::net
diff --git a/ResolverController.cpp b/ResolverController.cpp
index 6a10326..7b181e2 100644
--- a/ResolverController.cpp
+++ b/ResolverController.cpp
@@ -22,8 +22,6 @@
 #include <string>
 #include <vector>
 
-#include <netdb.h>
-
 #include <aidl/android/net/IDnsResolver.h>
 #include <android-base/logging.h>
 #include <android-base/strings.h>
@@ -46,41 +44,6 @@
 
 namespace {
 
-std::string addrToString(const sockaddr_storage* addr) {
-    char out[INET6_ADDRSTRLEN] = {0};
-    getnameinfo((const sockaddr*)addr, sizeof(sockaddr_storage), out, INET6_ADDRSTRLEN, nullptr, 0,
-                NI_NUMERICHOST);
-    return std::string(out);
-}
-
-const char* getPrivateDnsModeString(PrivateDnsMode mode) {
-    switch (mode) {
-        case PrivateDnsMode::OFF:
-            return "OFF";
-        case PrivateDnsMode::OPPORTUNISTIC:
-            return "OPPORTUNISTIC";
-        case PrivateDnsMode::STRICT:
-            return "STRICT";
-    }
-}
-
-constexpr const char* validationStatusToString(Validation value) {
-    switch (value) {
-        case Validation::in_process:
-            return "in_process";
-        case Validation::success:
-            return "success";
-        case Validation::fail:
-            return "fail";
-        case Validation::unknown_server:
-            return "unknown_server";
-        case Validation::unknown_netid:
-            return "unknown_netid";
-        default:
-            return "unknown_status";
-    }
-}
-
 void sendNat64PrefixEvent(const Dns64Configuration::Nat64PrefixInfo& args) {
     const auto& listeners = ResolverEventReporter::getInstance().getListeners();
     if (listeners.size() == 0) {
@@ -201,6 +164,10 @@
 int ResolverController::setResolverConfiguration(const ResolverParamsParcel& resolverParams) {
     using aidl::android::net::IDnsResolver;
 
+    if (!has_named_cache(resolverParams.netId)) {
+        return -ENOENT;
+    }
+
     // Expect to get the mark with system permission.
     android_net_context netcontext;
     gResNetdCallbacks.get_network_context(resolverParams.netId, 0 /* uid */, &netcontext);
@@ -223,6 +190,10 @@
         return err;
     }
 
+    if (int err = resolv_stats_set_servers_for_dot(resolverParams.netId, tlsServers); err != 0) {
+        return err;
+    }
+
     res_params res_params = {};
     res_params.sample_validity = resolverParams.sampleValiditySeconds;
     res_params.success_threshold = resolverParams.successThreshold;
@@ -255,8 +226,8 @@
     ResolverStats::encodeAll(res_stats, stats);
 
     const auto privateDnsStatus = PrivateDnsConfiguration::getInstance().getStatus(netId);
-    for (const auto& pair : privateDnsStatus.serversMap) {
-        tlsServers->push_back(addrToString(&pair.first.ss));
+    for (const auto& [server, _] : privateDnsStatus.serversMap) {
+        tlsServers->push_back(server.toIpString());
     }
 
     params->resize(IDnsResolver::RESOLVER_PARAMS_COUNT);
@@ -355,9 +326,9 @@
             dw.println("Private DNS configuration (%u entries)",
                        static_cast<uint32_t>(privateDnsStatus.serversMap.size()));
             dw.incIndent();
-            for (const auto& pair : privateDnsStatus.serversMap) {
-                dw.println("%s name{%s} status{%s}", addrToString(&pair.first.ss).c_str(),
-                           pair.first.name.c_str(), validationStatusToString(pair.second));
+            for (const auto& [server, validation] : privateDnsStatus.serversMap) {
+                dw.println("%s name{%s} status{%s}", server.toIpString().c_str(),
+                           server.name.c_str(), validationStatusToString(validation));
             }
             dw.decIndent();
         }
diff --git a/aidl_api/dnsresolver_aidl_interface/7/.hash b/aidl_api/dnsresolver_aidl_interface/7/.hash
new file mode 100644
index 0000000..75f2e38
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/.hash
@@ -0,0 +1 @@
+a1dc9394598357ccaa74e96f564e7f248b72bad2
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/IDnsResolver.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/IDnsResolver.aidl
new file mode 100644
index 0000000..1f80545
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/IDnsResolver.aidl
@@ -0,0 +1,66 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface IDnsResolver {
+  boolean isAlive();
+  void registerEventListener(android.net.metrics.INetdEventListener listener);
+  void setResolverConfiguration(in android.net.ResolverParamsParcel resolverParams);
+  void getResolverInfo(int netId, out @utf8InCpp String[] servers, out @utf8InCpp String[] domains, out @utf8InCpp String[] tlsServers, out int[] params, out int[] stats, out int[] wait_for_pending_req_timeout_count);
+  void startPrefix64Discovery(int netId);
+  void stopPrefix64Discovery(int netId);
+  @utf8InCpp String getPrefix64(int netId);
+  void createNetworkCache(int netId);
+  void destroyNetworkCache(int netId);
+  void setLogSeverity(int logSeverity);
+  void flushNetworkCache(int netId);
+  void setPrefix64(int netId, @utf8InCpp String prefix);
+  void registerUnsolicitedEventListener(android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener listener);
+  const int RESOLVER_PARAMS_SAMPLE_VALIDITY = 0;
+  const int RESOLVER_PARAMS_SUCCESS_THRESHOLD = 1;
+  const int RESOLVER_PARAMS_MIN_SAMPLES = 2;
+  const int RESOLVER_PARAMS_MAX_SAMPLES = 3;
+  const int RESOLVER_PARAMS_BASE_TIMEOUT_MSEC = 4;
+  const int RESOLVER_PARAMS_RETRY_COUNT = 5;
+  const int RESOLVER_PARAMS_COUNT = 6;
+  const int RESOLVER_STATS_SUCCESSES = 0;
+  const int RESOLVER_STATS_ERRORS = 1;
+  const int RESOLVER_STATS_TIMEOUTS = 2;
+  const int RESOLVER_STATS_INTERNAL_ERRORS = 3;
+  const int RESOLVER_STATS_RTT_AVG = 4;
+  const int RESOLVER_STATS_LAST_SAMPLE_TIME = 5;
+  const int RESOLVER_STATS_USABLE = 6;
+  const int RESOLVER_STATS_COUNT = 7;
+  const int DNS_RESOLVER_LOG_VERBOSE = 0;
+  const int DNS_RESOLVER_LOG_DEBUG = 1;
+  const int DNS_RESOLVER_LOG_INFO = 2;
+  const int DNS_RESOLVER_LOG_WARNING = 3;
+  const int DNS_RESOLVER_LOG_ERROR = 4;
+  const int TC_MODE_DEFAULT = 0;
+  const int TC_MODE_UDP_TCP = 1;
+  const int TRANSPORT_UNKNOWN = -1;
+  const int TRANSPORT_CELLULAR = 0;
+  const int TRANSPORT_WIFI = 1;
+  const int TRANSPORT_BLUETOOTH = 2;
+  const int TRANSPORT_ETHERNET = 3;
+  const int TRANSPORT_VPN = 4;
+  const int TRANSPORT_WIFI_AWARE = 5;
+  const int TRANSPORT_LOWPAN = 6;
+  const int TRANSPORT_TEST = 7;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverHostsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverHostsParcel.aidl
new file mode 100644
index 0000000..c24eb61
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverHostsParcel.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable ResolverHostsParcel {
+  @utf8InCpp String ipAddr;
+  @utf8InCpp String hostName = "";
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverOptionsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverOptionsParcel.aidl
new file mode 100644
index 0000000..e806d04
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverOptionsParcel.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable ResolverOptionsParcel {
+  android.net.ResolverHostsParcel[] hosts = {};
+  int tcMode = 0;
+  boolean enforceDnsUid = false;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverParamsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverParamsParcel.aidl
new file mode 100644
index 0000000..8fec710
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverParamsParcel.aidl
@@ -0,0 +1,38 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable ResolverParamsParcel {
+  int netId;
+  int sampleValiditySeconds;
+  int successThreshold;
+  int minSamples;
+  int maxSamples;
+  int baseTimeoutMsec;
+  int retryCount;
+  @utf8InCpp String[] servers;
+  @utf8InCpp String[] domains;
+  @utf8InCpp String tlsName;
+  @utf8InCpp String[] tlsServers;
+  @utf8InCpp String[] tlsFingerprints = {};
+  @utf8InCpp String caCertificate = "";
+  int tlsConnectTimeoutMs = 0;
+  android.net.ResolverOptionsParcel resolverOptions;
+  int[] transportTypes = {};
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/DnsHealthEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/DnsHealthEventParcel.aidl
new file mode 100644
index 0000000..d32be91
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/DnsHealthEventParcel.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable DnsHealthEventParcel {
+  int netId;
+  int healthResult;
+  int[] successRttMicros;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..d8accd1
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
@@ -0,0 +1,31 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.resolv.aidl;
+/* @hide */
+interface IDnsResolverUnsolicitedEventListener {
+  oneway void onDnsHealthEvent(in android.net.resolv.aidl.DnsHealthEventParcel dnsHealthEvent);
+  oneway void onNat64PrefixEvent(in android.net.resolv.aidl.Nat64PrefixEventParcel nat64PrefixEvent);
+  oneway void onPrivateDnsValidationEvent(in android.net.resolv.aidl.PrivateDnsValidationEventParcel privateDnsValidationEvent);
+  const int DNS_HEALTH_RESULT_OK = 0;
+  const int DNS_HEALTH_RESULT_TIMEOUT = 255;
+  const int PREFIX_OPERATION_ADDED = 1;
+  const int PREFIX_OPERATION_REMOVED = 2;
+  const int VALIDATION_RESULT_SUCCESS = 1;
+  const int VALIDATION_RESULT_FAILURE = 2;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
new file mode 100644
index 0000000..2daccb0
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable Nat64PrefixEventParcel {
+  int netId;
+  int prefixOperation;
+  @utf8InCpp String prefixAddress;
+  int prefixLength;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
new file mode 100644
index 0000000..e66e21c
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable PrivateDnsValidationEventParcel {
+  int netId;
+  @utf8InCpp String ipAddress;
+  @utf8InCpp String hostname;
+  int validation;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/IDnsResolver.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/IDnsResolver.aidl
index 863927b..1f80545 100644
--- a/aidl_api/dnsresolver_aidl_interface/current/android/net/IDnsResolver.aidl
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/IDnsResolver.aidl
@@ -2,13 +2,14 @@
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
@@ -30,6 +31,7 @@
   void setLogSeverity(int logSeverity);
   void flushNetworkCache(int netId);
   void setPrefix64(int netId, @utf8InCpp String prefix);
+  void registerUnsolicitedEventListener(android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener listener);
   const int RESOLVER_PARAMS_SAMPLE_VALIDITY = 0;
   const int RESOLVER_PARAMS_SUCCESS_THRESHOLD = 1;
   const int RESOLVER_PARAMS_MIN_SAMPLES = 2;
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverHostsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverHostsParcel.aidl
index 3ab0533..c24eb61 100644
--- a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverHostsParcel.aidl
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverHostsParcel.aidl
@@ -2,13 +2,14 @@
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverOptionsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverOptionsParcel.aidl
index d55ae46..e806d04 100644
--- a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverOptionsParcel.aidl
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverOptionsParcel.aidl
@@ -2,13 +2,14 @@
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverParamsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverParamsParcel.aidl
index 5dae1ca..8fec710 100644
--- a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverParamsParcel.aidl
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverParamsParcel.aidl
@@ -2,13 +2,14 @@
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/DnsHealthEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/DnsHealthEventParcel.aidl
new file mode 100644
index 0000000..d32be91
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/DnsHealthEventParcel.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable DnsHealthEventParcel {
+  int netId;
+  int healthResult;
+  int[] successRttMicros;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..d8accd1
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
@@ -0,0 +1,31 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.resolv.aidl;
+/* @hide */
+interface IDnsResolverUnsolicitedEventListener {
+  oneway void onDnsHealthEvent(in android.net.resolv.aidl.DnsHealthEventParcel dnsHealthEvent);
+  oneway void onNat64PrefixEvent(in android.net.resolv.aidl.Nat64PrefixEventParcel nat64PrefixEvent);
+  oneway void onPrivateDnsValidationEvent(in android.net.resolv.aidl.PrivateDnsValidationEventParcel privateDnsValidationEvent);
+  const int DNS_HEALTH_RESULT_OK = 0;
+  const int DNS_HEALTH_RESULT_TIMEOUT = 255;
+  const int PREFIX_OPERATION_ADDED = 1;
+  const int PREFIX_OPERATION_REMOVED = 2;
+  const int VALIDATION_RESULT_SUCCESS = 1;
+  const int VALIDATION_RESULT_FAILURE = 2;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
new file mode 100644
index 0000000..2daccb0
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable Nat64PrefixEventParcel {
+  int netId;
+  int prefixOperation;
+  @utf8InCpp String prefixAddress;
+  int prefixLength;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
new file mode 100644
index 0000000..e66e21c
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable PrivateDnsValidationEventParcel {
+  int netId;
+  @utf8InCpp String ipAddress;
+  @utf8InCpp String hostname;
+  int validation;
+}
diff --git a/binder/android/net/IDnsResolver.aidl b/binder/android/net/IDnsResolver.aidl
index 538c3b8..33316e3 100644
--- a/binder/android/net/IDnsResolver.aidl
+++ b/binder/android/net/IDnsResolver.aidl
@@ -18,6 +18,7 @@
 
 import android.net.ResolverParamsParcel;
 import android.net.metrics.INetdEventListener;
+import android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener;
 
 /** {@hide} */
 interface IDnsResolver {
@@ -145,7 +146,7 @@
      */
     void destroyNetworkCache(int netId);
 
-    // Refer to enum LogSeverity from system/core/base/include/android-base/logging.h
+    // Refer to enum LogSeverity from system/libbase/include/android-base/logging.h
     const int DNS_RESOLVER_LOG_VERBOSE = 0;
     const int DNS_RESOLVER_LOG_DEBUG = 1;
     const int DNS_RESOLVER_LOG_INFO = 2;
@@ -214,4 +215,21 @@
      *         unix errno.
      */
     void setPrefix64(int netId, @utf8InCpp String prefix);
+
+    /**
+    * Register unsolicited event listener
+    * DnsResolver supports multiple unsolicited event listeners.
+    *
+    * This is a non-public interface between DnsResolver and Connectivity/NetworkStack.
+    * It is subject to change on Mainline updates without notice. DO NOT DEPEND ON IT.
+    *
+    * Only system services(Connectivity/NetworkStack) will register the unsolicited listener.
+    * Besides, there is no unregister method since the system services will be always there to
+    * listen unsolicited events.
+    *
+    * @param listener unsolicited event listener to register
+    * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+    *         unix errno.
+    */
+    void registerUnsolicitedEventListener(IDnsResolverUnsolicitedEventListener listener);
 }
diff --git a/binder/android/net/resolv/aidl/DnsHealthEventParcel.aidl b/binder/android/net/resolv/aidl/DnsHealthEventParcel.aidl
new file mode 100644
index 0000000..33f60da
--- /dev/null
+++ b/binder/android/net/resolv/aidl/DnsHealthEventParcel.aidl
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2020, 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.resolv.aidl;
+
+/**
+ * The DNS health result of queries.
+ *
+ * Only sent for API calls that actually generated network traffic:
+ *  - No cache hits
+ *  - No power management errors (rcode == INTERNAL_ERROR && errno == EPERM)
+ *  - Only lookup result is OK or TIMEOUT.
+ *
+ * This must not be used for use-cases other than evaluating the health of DNS queries. The behavior
+ * of this interface will change across regular module updates, and in particular it will be updated
+ * to stop sending one event by DNS API call, and instead report network failures in realtime.
+ *
+ * Non-public API, only callable from Connectivity or NetworkStack Mainline modules.
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable DnsHealthEventParcel {
+
+    /*** The ID of the network that lookup was performed on. */
+    int netId;
+
+    /**
+     * The health result of queries.
+     * Currently, this value is for data stall detection and it only checks whether result is
+     * timeout or not. So if the result is neither OK nor TIMEOUT, will not send this event.
+     */
+    int healthResult;
+
+    /*** The round trip time for each dns query that received a reply */
+    int[] successRttMicros;
+}
diff --git a/binder/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl b/binder/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..b0d6553
--- /dev/null
+++ b/binder/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2020, 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.resolv.aidl;
+
+import android.net.resolv.aidl.DnsHealthEventParcel;
+import android.net.resolv.aidl.Nat64PrefixEventParcel;
+import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
+
+/**
+ * Unsolicited dns resolver events listener.
+ * This one-way interface groups asynchronous notifications sent by dns resolver to any process that
+ * registered itself via IDnsResolver.registerUnsolicitedEventListener.
+ *
+ * {@hide}
+ */
+oneway interface IDnsResolverUnsolicitedEventListener {
+
+    /*** Types for {@code healthResult} of {@code DnsHealthEventParcel}. */
+    const int DNS_HEALTH_RESULT_OK = 0;
+    const int DNS_HEALTH_RESULT_TIMEOUT = 255;
+
+    /**
+     * Represents a DNS health result of queries.
+     *
+     * Sent by the resolver when it has events to report about its observed network health.
+     * Refer to DnsHealthEventParcel for more details.
+     *
+     * @param dnsHealthEvent the DNS health result.
+     */
+    void onDnsHealthEvent(in DnsHealthEventParcel dnsHealthEvent);
+
+    /*** Types for {@code prefixOperation} of {@code Nat64PrefixEventParcel}. */
+    const int PREFIX_OPERATION_ADDED = 1;
+    const int PREFIX_OPERATION_REMOVED = 2;
+
+    /**
+     * Represents a NAT64 prefix operation.
+     *
+     * @param nat64PrefixEvent the NAT64 prefix status.
+     */
+    void onNat64PrefixEvent(in Nat64PrefixEventParcel nat64PrefixEvent);
+
+    /*** Types for {@code validation} of {@code PrivateDnsValidationEventParcel}. */
+    const int VALIDATION_RESULT_SUCCESS = 1;
+    const int VALIDATION_RESULT_FAILURE = 2;
+
+    /**
+     * Represents a private DNS validation result.
+     *
+     * @param privateDnsValidationEvent the private DNS validation result.
+     */
+    void onPrivateDnsValidationEvent(in PrivateDnsValidationEventParcel privateDnsValidationEvent);
+}
diff --git a/binder/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl b/binder/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
new file mode 100644
index 0000000..45e25a1
--- /dev/null
+++ b/binder/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2020, 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.resolv.aidl;
+
+/**
+ * Nat64 prefix operation event.
+ *
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable Nat64PrefixEventParcel {
+
+    /** The ID of the network the prefix operation was performed on. */
+    int netId;
+
+    /**
+     * The NAT64 prefix operation.
+     * There is only one prefix at a time for each netId. If a prefix is added, it replaces the
+     * previous-added prefix.
+     */
+    int prefixOperation;
+
+    /** The detected NAT64 prefix address. */
+    @utf8InCpp String prefixAddress;
+
+    /** The prefix length associated with this NAT64 prefix. */
+    int prefixLength;
+}
diff --git a/binder/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl b/binder/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
new file mode 100644
index 0000000..e5d7f78
--- /dev/null
+++ b/binder/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2020, 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.resolv.aidl;
+
+/**
+ * A private DNS validation result.
+ *
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable PrivateDnsValidationEventParcel {
+
+    /** The ID of the network the validation was performed on. */
+    int netId;
+
+    /** The IP address for which validation was performed. */
+    @utf8InCpp String ipAddress;
+
+    /** The hostname for which validation was performed. */
+    @utf8InCpp String hostname;
+
+    /** The validation result. */
+    int validation;
+}
diff --git a/getaddrinfo.cpp b/getaddrinfo.cpp
index 4804762..b3911fc 100644
--- a/getaddrinfo.cpp
+++ b/getaddrinfo.cpp
@@ -62,7 +62,6 @@
 #include "netd_resolv/resolv.h"
 #include "res_comp.h"
 #include "res_debug.h"
-#include "res_init.h"
 #include "resolv_cache.h"
 #include "resolv_private.h"
 #include "util.h"
@@ -1436,8 +1435,7 @@
             return EAI_FAMILY;
     }
 
-    ResState res;
-    res_init(&res, netcontext, event);
+    ResState res(netcontext, event);
 
     int he;
     if (res_searchN(name, &q, &res, &he) < 0) {
@@ -1633,7 +1631,6 @@
     NetworkDnsEventReported event;
     if (n <= 0) {
         LOG(ERROR) << __func__ << ": res_nmkquery failed";
-        return {0, -1, NO_RECOVERY, event};
         return {
                 .ancount = 0,
                 .rcode = -1,
@@ -1642,7 +1639,7 @@
         };
     }
 
-    ResState res_temp = fromResState(*res, &event);
+    ResState res_temp = res->clone(&event);
 
     int rcode = NOERROR;
     n = res_nsend(&res_temp, buf, n, t->answer.data(), anslen, &rcode, 0, sleepTimeMs);
@@ -1710,7 +1707,7 @@
 
 static int res_queryN_wrapper(const char* name, res_target* target, res_state res, int* herrno) {
     const bool parallel_lookup =
-            android::net::Experiments::getInstance()->getFlag("parallel_lookup", 1);
+            android::net::Experiments::getInstance()->getFlag("parallel_lookup_release", 1);
     if (parallel_lookup) return res_queryN_parallel(name, target, res, herrno);
 
     return res_queryN(name, target, res, herrno);
diff --git a/gethnamaddr.cpp b/gethnamaddr.cpp
index 23f94bb..4d01c64 100644
--- a/gethnamaddr.cpp
+++ b/gethnamaddr.cpp
@@ -76,8 +76,8 @@
 #include "netd_resolv/resolv.h"
 #include "res_comp.h"
 #include "res_debug.h"  // p_class(), p_type()
-#include "res_init.h"
 #include "resolv_cache.h"
+#include "resolv_private.h"
 #include "stats.pb.h"
 
 using android::net::NetworkDnsEventReported;
@@ -388,10 +388,12 @@
 int resolv_gethostbyname(const char* name, int af, hostent* hp, char* buf, size_t buflen,
                          const android_net_context* netcontext, hostent** result,
                          NetworkDnsEventReported* event) {
-    getnamaddr info;
+    if (name == nullptr || hp == nullptr) {
+        return EAI_SYSTEM;
+    }
 
-    ResState res;
-    res_init(&res, netcontext, event);
+    getnamaddr info;
+    ResState res(netcontext, event);
 
     size_t size;
     switch (af) {
@@ -727,8 +729,7 @@
 
     auto buf = std::make_unique<querybuf>();
 
-    ResState res;
-    res_init(&res, netcontext, event);
+    ResState res(netcontext, event);
     int he;
     n = res_nquery(&res, qbuf, C_IN, T_PTR, buf->buf, (int)sizeof(buf->buf), &he);
     if (n < 0) {
diff --git a/res_cache.cpp b/res_cache.cpp
index ffa2929..dfd4a79 100644
--- a/res_cache.cpp
+++ b/res_cache.cpp
@@ -264,6 +264,22 @@
     const uint8_t* cursor;
 };
 
+static uint8_t res_tolower(uint8_t c) {
+    return (c >= 'A' && c <= 'Z') ? (c | 0x20) : c;
+}
+
+static int res_memcasecmp(const unsigned char *s1, const unsigned char *s2, size_t len) {
+    for (size_t i = 0; i < len; i++) {
+        int ch1 = *s1++;
+        int ch2 = *s2++;
+        int d = res_tolower(ch1) - res_tolower(ch2);
+        if (d != 0) {
+            return d;
+        }
+    }
+    return 0;
+}
+
 static void _dnsPacket_init(DnsPacket* packet, const uint8_t* buff, int bufflen) {
     packet->base = buff;
     packet->end = buff + bufflen;
@@ -451,14 +467,12 @@
     const uint8_t* end = packet->end;
 
     for (;;) {
-        int c;
-
         if (p >= end) { /* should not happen */
             LOG(INFO) << __func__ << ": INTERNAL_ERROR: read-overflow";
             break;
         }
 
-        c = *p++;
+        int c = *p++;
 
         if (c == 0) break;
 
@@ -470,9 +484,12 @@
             LOG(INFO) << __func__ << ": INTERNAL_ERROR: simple label read-overflow";
             break;
         }
+
         while (c > 0) {
-            hash = hash * FNV_MULT ^ *p++;
-            c -= 1;
+            uint8_t ch = *p++;
+            ch = res_tolower(ch);
+            hash = hash * FNV_MULT ^ ch;
+            c--;
         }
     }
     packet->cursor = p;
@@ -548,14 +565,12 @@
     const uint8_t* end2 = pack2->end;
 
     for (;;) {
-        int c1, c2;
-
         if (p1 >= end1 || p2 >= end2) {
             LOG(INFO) << __func__ << ": INTERNAL_ERROR: read-overflow";
             break;
         }
-        c1 = *p1++;
-        c2 = *p2++;
+        int c1 = *p1++;
+        int c2 = *p2++;
         if (c1 != c2) break;
 
         if (c1 == 0) {
@@ -571,7 +586,7 @@
             LOG(INFO) << __func__ << ": INTERNAL_ERROR: simple label read-overflow";
             break;
         }
-        if (memcmp(p1, p2, c1) != 0) break;
+        if (res_memcasecmp(p1, p2, c1) != 0) break;
         p1 += c1;
         p2 += c1;
         /* we rely on the bound checks at the start of the loop */
@@ -1001,9 +1016,13 @@
     // 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.
+    typedef std::multimap<std::string /* hostname */, std::string /* IPv4/IPv6 address */>
+            HostMapping;
     HostMapping customizedTable = {};
+
     int tc_mode = aidl::android::net::IDnsResolver::TC_MODE_DEFAULT;
     bool enforceDnsUid = false;
     std::vector<int32_t> transportTypes;
diff --git a/res_init.cpp b/res_init.cpp
deleted file mode 100644
index a0004c4..0000000
--- a/res_init.cpp
+++ /dev/null
@@ -1,133 +0,0 @@
-/*	$NetBSD: res_init.c,v 1.8 2006/03/19 03:10:08 christos Exp $	*/
-
-/*
- * Copyright (c) 1985, 1989, 1993
- *    The Regents of the University of California.  All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- *    must display the following acknowledgement:
- * 	This product includes software developed by the University of
- * 	California, Berkeley and its contributors.
- * 4. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-/*
- * Portions Copyright (c) 1993 by Digital Equipment Corporation.
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies, and that
- * the name of Digital Equipment Corporation not be used in advertising or
- * publicity pertaining to distribution of the document or software without
- * specific, written prior permission.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
- * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
- * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
- * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
- * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
- * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
- * SOFTWARE.
- */
-
-/*
- * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
- * Portions Copyright (c) 1996-1999 by Internet Software Consortium.
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
- * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define LOG_TAG "resolv"
-
-#include <sys/param.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-
-#include <arpa/inet.h>
-#include <arpa/nameser.h>
-#include <netinet/in.h>
-
-#include <android-base/logging.h>
-#include <ctype.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <netdb.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "netd_resolv/resolv.h"
-#include "resolv_private.h"
-#include "stats.pb.h"
-
-void res_init(ResState* statp, const struct android_net_context* _Nonnull netcontext,
-              android::net::NetworkDnsEventReported* _Nonnull event) {
-    statp->netid = netcontext->dns_netid;
-    statp->uid = netcontext->uid;
-    statp->pid = netcontext->pid;
-    statp->id = arc4random_uniform(65536);
-
-    for (auto& sock : statp->nssocks) {
-        sock.reset();
-    }
-    statp->ndots = 1;
-    statp->_mark = netcontext->dns_mark;
-    statp->tcp_nssock.reset();
-    statp->event = event;
-    statp->netcontext_flags = netcontext->flags;
-}
-
-// TODO: Have some proper constructors for ResState instead of this method and res_init().
-ResState fromResState(const ResState& other, android::net::NetworkDnsEventReported* event) {
-    ResState resOutput;
-    resOutput.netid = other.netid;
-    resOutput.uid = other.uid;
-    resOutput.pid = other.pid;
-    resOutput.id = other.id;
-
-    resOutput.nsaddrs = other.nsaddrs;
-
-    for (auto& sock : resOutput.nssocks) {
-        sock.reset();
-    }
-
-    resOutput.ndots = other.ndots;
-    resOutput._mark = other._mark;
-    resOutput.tcp_nssock.reset();
-    resOutput.event = event;
-    resOutput.netcontext_flags = other.netcontext_flags;
-    return resOutput;
-}
diff --git a/res_init.h b/res_init.h
deleted file mode 100644
index 0cfd7b9..0000000
--- a/res_init.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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.
- */
-#pragma once
-
-#include "resolv_private.h"
-#include "stats.pb.h"
-
-// TODO: make this a constructor for ResState
-void res_init(ResState* res, const struct android_net_context* netcontext,
-              android::net::NetworkDnsEventReported* event);
-ResState fromResState(const ResState& other, android::net::NetworkDnsEventReported* event);
diff --git a/res_send.cpp b/res_send.cpp
index 3638e05..c78e02f 100644
--- a/res_send.cpp
+++ b/res_send.cpp
@@ -111,7 +111,6 @@
 
 #include "res_comp.h"
 #include "res_debug.h"
-#include "res_init.h"
 #include "resolv_cache.h"
 #include "stats.h"
 #include "stats.pb.h"
@@ -144,8 +143,6 @@
 using android::netdutils::Slice;
 using android::netdutils::Stopwatch;
 
-static DnsTlsDispatcher sDnsTlsDispatcher;
-
 static int send_vc(res_state statp, res_params* params, const uint8_t* buf, int buflen,
                    uint8_t* ans, int anssiz, int* terrno, size_t ns, time_t* at, int* rcode,
                    int* delay);
@@ -1255,8 +1252,8 @@
 
     LOG(INFO) << __func__ << ": performing query over TLS";
 
-    const auto response = sDnsTlsDispatcher.query(privateDnsStatus.validatedServers(), statp, query,
-                                                  answer, &resplen);
+    const auto response = DnsTlsDispatcher::getInstance().query(privateDnsStatus.validatedServers(),
+                                                                statp, query, answer, &resplen);
 
     LOG(INFO) << __func__ << ": TLS query result: " << static_cast<int>(response);
 
@@ -1300,8 +1297,7 @@
                      uint8_t* ans, int ansLen, int* rcode, uint32_t flags,
                      NetworkDnsEventReported* event) {
     assert(event != nullptr);
-    ResState res;
-    res_init(&res, netContext, event);
+    ResState res(netContext, event);
     resolv_populate_res_for_net(&res);
     *rcode = NOERROR;
     return res_nsend(&res, msg, msgLen, ans, ansLen, rcode, flags);
diff --git a/resolv_cache.h b/resolv_cache.h
index d8a3afc..15baa14 100644
--- a/resolv_cache.h
+++ b/resolv_cache.h
@@ -46,9 +46,6 @@
 // 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();
@@ -100,7 +97,6 @@
 // Get transport types to a given network.
 android::net::NetworkType resolv_get_network_types_for_net(unsigned netid);
 
-// For test only.
 // Return true if the cache is existent in the given network, false otherwise.
 bool has_named_cache(unsigned netid);
 
diff --git a/resolv_cache_unit_test.cpp b/resolv_cache_unit_test.cpp
index d604b2d..002bdaa 100644
--- a/resolv_cache_unit_test.cpp
+++ b/resolv_cache_unit_test.cpp
@@ -30,17 +30,16 @@
 #include <gmock/gmock-matchers.h>
 #include <gtest/gtest.h>
 
-#include "res_init.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;
 
-constexpr int TEST_NETID = 30;
 constexpr int TEST_NETID_2 = 31;
 constexpr int DNS_PORT = 53;
 
@@ -227,7 +226,7 @@
         // Server checking.
         EXPECT_EQ(nscount, static_cast<int>(expected.setup.servers.size())) << msg;
         for (int i = 0; i < nscount; i++) {
-            EXPECT_EQ(addrToString(&servers[i]), expected.setup.servers[i]) << msg;
+            EXPECT_EQ(ToString(&servers[i]), expected.setup.servers[i]) << msg;
         }
 
         // Domain checking
diff --git a/resolv_private.h b/resolv_private.h
index 8bc5035..bc7e415 100644
--- a/resolv_private.h
+++ b/resolv_private.h
@@ -88,6 +88,33 @@
 constexpr int MAXPACKET = 8 * 1024;
 
 struct ResState {
+    ResState(const android_net_context* netcontext, android::net::NetworkDnsEventReported* dnsEvent)
+        : netid(netcontext->dns_netid),
+          uid(netcontext->uid),
+          pid(netcontext->pid),
+          _mark(netcontext->dns_mark),
+          event(dnsEvent),
+          netcontext_flags(netcontext->flags) {}
+
+    ResState clone(android::net::NetworkDnsEventReported* dnsEvent = nullptr) {
+        // TODO: Separate non-copyable members to other structures and let default copy
+        //       constructor do its work for below copyable members.
+        ResState copy;
+        copy.netid = netid;
+        copy.uid = uid;
+        copy.pid = pid;
+        copy.search_domains = search_domains;
+        copy.nsaddrs = nsaddrs;
+        copy.ndots = ndots;
+        copy._mark = _mark;
+        copy._flags = _flags;
+        copy.event = (dnsEvent == nullptr) ? event : dnsEvent;
+        copy.netcontext_flags = netcontext_flags;
+        copy.tc_mode = tc_mode;
+        copy.enforce_dns_uid = enforce_dns_uid;
+        copy.sort_nameservers = sort_nameservers;
+        return copy;
+    }
     void closeSockets() {
         tcp_nssock.reset();
         _flags &= ~RES_F_VC;
@@ -103,11 +130,10 @@
     unsigned netid;                             // NetId: cache key and socket mark
     uid_t uid;                                  // uid of the app that sent the DNS lookup
     pid_t pid;                                  // pid of the app that sent the DNS lookup
-    uint16_t id;                                // current message id
     std::vector<std::string> search_domains{};  // domains to search
     std::vector<android::netdutils::IPSockAddr> nsaddrs;
     android::base::unique_fd nssocks[MAXNS];    // UDP sockets to nameservers
-    unsigned ndots : 4;                         // threshold for initial abs. query
+    unsigned ndots : 4 = 1;                     // threshold for initial abs. query
     unsigned _mark;                             // If non-0 SET_MARK to _mark on all request sockets
     android::base::unique_fd tcp_nssock;        // TCP socket (but why not one per nameserver?)
     uint32_t _flags = 0;                        // See RES_F_* defines below
@@ -118,6 +144,8 @@
     bool sort_nameservers = false;              // A flag to indicate whether nsaddrs has been
                                                 // sorted or not.
     // clang-format on
+  private:
+    ResState() {}
 };
 
 // TODO: remove these legacy aliases
@@ -183,10 +211,3 @@
         PLOG(WARNING) << "Failed to chown socket";
     }
 }
-
-inline std::string addrToString(const sockaddr_storage* addr) {
-    char out[INET6_ADDRSTRLEN] = {0};
-    getnameinfo((const sockaddr*)addr, sizeof(sockaddr_storage), out, INET6_ADDRSTRLEN, nullptr, 0,
-                NI_NUMERICHOST);
-    return std::string(out);
-}
diff --git a/resolv_tls_unit_test.cpp b/resolv_tls_unit_test.cpp
index 5a03db7..29171db 100644
--- a/resolv_tls_unit_test.cpp
+++ b/resolv_tls_unit_test.cpp
@@ -22,6 +22,7 @@
 
 #include <android-base/logging.h>
 #include <android-base/macros.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <netdutils/Slice.h>
 
@@ -31,6 +32,7 @@
 #include "DnsTlsSessionCache.h"
 #include "DnsTlsSocket.h"
 #include "DnsTlsTransport.h"
+#include "Experiments.h"
 #include "IDnsTlsSocket.h"
 #include "IDnsTlsSocketFactory.h"
 #include "IDnsTlsSocketObserver.h"
@@ -39,8 +41,10 @@
 namespace android {
 namespace net {
 
-using netdutils::Slice;
 using netdutils::makeSlice;
+using netdutils::Slice;
+
+static const std::string DOT_MAXTRIES_FLAG = "dot_maxtries";
 
 typedef std::vector<uint8_t> bytevec;
 
@@ -134,6 +138,7 @@
         std::thread(&IDnsTlsSocketObserver::onResponse, mObserver, make_echo(id, query)).detach();
         return true;
     }
+    bool startHandshake() override { return true; }
 
   private:
     IDnsTlsSocketObserver* const mObserver;
@@ -169,6 +174,7 @@
         std::thread(&IDnsTlsSocketObserver::onResponse, mObserver, response).detach();
         return true;
     }
+    bool startHandshake() override { return true; }
 
   private:
     IDnsTlsSocketObserver* const mObserver;
@@ -216,9 +222,15 @@
 class FakeSocketDelay : public IDnsTlsSocket {
   public:
     explicit FakeSocketDelay(IDnsTlsSocketObserver* observer) : mObserver(observer) {}
-    ~FakeSocketDelay() { std::lock_guard guard(mLock); }
-    static size_t sDelay;
-    static bool sReverse;
+    ~FakeSocketDelay() {
+        std::lock_guard guard(mLock);
+        sDelay = 1;
+        sReverse = false;
+        sConnectable = true;
+    }
+    inline static size_t sDelay = 1;
+    inline static bool sReverse = false;
+    inline static bool sConnectable = true;
 
     bool query(uint16_t id, const Slice query) override {
         LOG(DEBUG) << "FakeSocketDelay got query with ID " << int(id);
@@ -236,6 +248,7 @@
         }
         return true;
     }
+    bool startHandshake() override { return sConnectable; }
 
   private:
     void sendResponses() {
@@ -256,9 +269,6 @@
     std::vector<bytevec> mResponses GUARDED_BY(mLock);
 };
 
-size_t FakeSocketDelay::sDelay;
-bool FakeSocketDelay::sReverse;
-
 TEST_F(TransportTest, ParallelColliding) {
     FakeSocketDelay::sDelay = 10;
     FakeSocketDelay::sReverse = false;
@@ -424,13 +434,24 @@
 };
 
 TEST_F(TransportTest, ConnectFail) {
-    NullSocketFactory factory;
-    DnsTlsTransport transport(SERVER1, MARK, &factory);
-    auto r = transport.query(makeSlice(QUERY)).get();
+    // Failure on creating socket.
+    NullSocketFactory factory1;
+    DnsTlsTransport transport1(SERVER1, MARK, &factory1);
+    auto r = transport1.query(makeSlice(QUERY)).get();
 
     EXPECT_EQ(DnsTlsTransport::Response::network_error, r.code);
     EXPECT_TRUE(r.response.empty());
-    EXPECT_EQ(transport.getConnectCounter(), 1);
+    EXPECT_EQ(transport1.getConnectCounter(), 1);
+
+    // Failure on handshaking.
+    FakeSocketDelay::sConnectable = false;
+    FakeSocketFactory<FakeSocketDelay> factory2;
+    DnsTlsTransport transport2(SERVER1, MARK, &factory2);
+    r = transport2.query(makeSlice(QUERY)).get();
+
+    EXPECT_EQ(DnsTlsTransport::Response::network_error, r.code);
+    EXPECT_TRUE(r.response.empty());
+    EXPECT_EQ(transport2.getConnectCounter(), 1);
 }
 
 // Simulate a socket that connects but then immediately receives a server
@@ -444,6 +465,7 @@
                const Slice query ATTRIBUTE_UNUSED) override {
         return true;
     }
+    bool startHandshake() override { return true; }
 
   private:
     std::thread mCloser;
@@ -457,8 +479,9 @@
     EXPECT_EQ(DnsTlsTransport::Response::network_error, r.code);
     EXPECT_TRUE(r.response.empty());
 
-    // Reconnections are triggered since DnsTlsQueryMap is not empty.
-    EXPECT_EQ(transport.getConnectCounter(), DnsTlsQueryMap::kMaxTries);
+    // Reconnections might be triggered depending on the flag.
+    EXPECT_EQ(transport.getConnectCounter(),
+              Experiments::getInstance()->getFlag(DOT_MAXTRIES_FLAG, DnsTlsQueryMap::kMaxTries));
 }
 
 // Simulate a server that occasionally closes the connection and silently
@@ -506,6 +529,7 @@
         }
         return mQueries <= sLimit;
     }
+    bool startHandshake() override { return true; }
 
   private:
     void sendClose() {
@@ -552,8 +576,9 @@
         EXPECT_TRUE(r.response.empty());
     }
 
-    // Reconnections are triggered since DnsTlsQueryMap is not empty.
-    EXPECT_EQ(transport.getConnectCounter(), DnsTlsQueryMap::kMaxTries);
+    // Reconnections might be triggered depending on the flag.
+    EXPECT_EQ(transport.getConnectCounter(),
+              Experiments::getInstance()->getFlag(DOT_MAXTRIES_FLAG, DnsTlsQueryMap::kMaxTries));
 }
 
 TEST_F(TransportTest, PartialDrop) {
@@ -632,6 +657,7 @@
         mThreads.emplace_back(&IDnsTlsSocketObserver::onResponse, mObserver, make_query(id + 1, query.size() + 2));
         return true;
     }
+    bool startHandshake() override { return true; }
 
   private:
     std::mutex mLock;
@@ -774,6 +800,18 @@
     EXPECT_FALSE(s2 == s1);
 }
 
+void checkEqual(const DnsTlsServer& s1, const DnsTlsServer& s2) {
+    EXPECT_TRUE(s1 == s1);
+    EXPECT_TRUE(s2 == s2);
+    EXPECT_TRUE(isAddressEqual(s1, s1));
+    EXPECT_TRUE(isAddressEqual(s2, s2));
+
+    EXPECT_FALSE(s1 < s2);
+    EXPECT_FALSE(s2 < s1);
+    EXPECT_TRUE(s1 == s2);
+    EXPECT_TRUE(s2 == s1);
+}
+
 class ServerTest : public BaseTest {};
 
 TEST_F(ServerTest, IPv4) {
@@ -824,12 +862,16 @@
     parseServer("192.0.2.1", 854, &s2.ss);
     checkUnequal(s1, s2);
     EXPECT_TRUE(isAddressEqual(s1, s2));
+    EXPECT_EQ(s1.toIpString(), "192.0.2.1");
+    EXPECT_EQ(s2.toIpString(), "192.0.2.1");
 
     DnsTlsServer s3, s4;
     parseServer("2001:db8::1", 853, &s3.ss);
     parseServer("2001:db8::1", 852, &s4.ss);
     checkUnequal(s3, s4);
     EXPECT_TRUE(isAddressEqual(s3, s4));
+    EXPECT_EQ(s3.toIpString(), "2001:db8::1");
+    EXPECT_EQ(s4.toIpString(), "2001:db8::1");
 
     EXPECT_FALSE(s1.wasExplicitlyConfigured());
     EXPECT_FALSE(s2.wasExplicitlyConfigured());
@@ -847,16 +889,22 @@
     EXPECT_TRUE(s2.wasExplicitlyConfigured());
 }
 
-TEST_F(ServerTest, Timeout) {
+TEST_F(ServerTest, State) {
     DnsTlsServer s1(V4ADDR1), s2(V4ADDR1);
-    s1.connectTimeout = std::chrono::milliseconds(4000);
-    checkUnequal(s1, s2);
-    s2.connectTimeout = std::chrono::milliseconds(4000);
-    EXPECT_EQ(s1, s2);
-    EXPECT_TRUE(isAddressEqual(s1, s2));
+    checkEqual(s1, s2);
+    s1.setValidationState(Validation::success);
+    checkEqual(s1, s2);
+    s2.setValidationState(Validation::fail);
+    checkEqual(s1, s2);
+    s1.setActive(true);
+    checkEqual(s1, s2);
+    s2.setActive(false);
+    checkEqual(s1, s2);
 
-    EXPECT_FALSE(s1.wasExplicitlyConfigured());
-    EXPECT_FALSE(s2.wasExplicitlyConfigured());
+    EXPECT_EQ(s1.validationState(), Validation::success);
+    EXPECT_EQ(s2.validationState(), Validation::fail);
+    EXPECT_TRUE(s1.active());
+    EXPECT_FALSE(s2.active());
 }
 
 TEST(QueryMapTest, Basic) {
@@ -959,44 +1007,119 @@
     EXPECT_FALSE(map.recordQuery(makeSlice(QUERY)));
 }
 
-class StubObserver : public IDnsTlsSocketObserver {
-  public:
-    bool closed = false;
-    void onResponse(std::vector<uint8_t>) override {}
+class DnsTlsSocketTest : public ::testing::Test {
+  protected:
+    class MockDnsTlsSocketObserver : public IDnsTlsSocketObserver {
+      public:
+        MOCK_METHOD(void, onClosed, (), (override));
+        MOCK_METHOD(void, onResponse, (std::vector<uint8_t>), (override));
+    };
 
-    void onClosed() override { closed = true; }
-};
+    DnsTlsSocketTest() { parseServer(kTlsAddr, std::stoi(kTlsPort), &server.ss); }
 
-TEST(DnsTlsSocketTest, SlowDestructor) {
-    constexpr char tls_addr[] = "127.0.0.3";
-    constexpr char tls_port[] = "8530";  // High-numbered port so root isn't required.
-    // This test doesn't perform any queries, so the backend address can be invalid.
-    constexpr char backend_addr[] = "192.0.2.1";
-    constexpr char backend_port[] = "1";
+    std::unique_ptr<DnsTlsSocket> makeDnsTlsSocket(IDnsTlsSocketObserver* observer) {
+        return std::make_unique<DnsTlsSocket>(this->server, MARK, observer, &this->cache);
+    }
 
-    test::DnsTlsFrontend tls(tls_addr, tls_port, backend_addr, backend_port);
-    ASSERT_TRUE(tls.startServer());
+    void enableAsyncHandshake(const std::unique_ptr<DnsTlsSocket>& socket) {
+        ASSERT_TRUE(socket);
+        DnsTlsSocket* delegate = socket.get();
+        std::lock_guard guard(delegate->mLock);
+        delegate->mAsyncHandshake = true;
+    }
+
+    static constexpr char kTlsAddr[] = "127.0.0.3";
+    static constexpr char kTlsPort[] = "8530";  // High-numbered port so root isn't required.
+    static constexpr char kBackendAddr[] = "192.0.2.1";
+    static constexpr char kBackendPort[] = "8531";  // High-numbered port so root isn't required.
+
+    test::DnsTlsFrontend tls{kTlsAddr, kTlsPort, kBackendAddr, kBackendPort};
 
     DnsTlsServer server;
-    parseServer(tls_addr, 8530, &server.ss);
-
-    StubObserver observer;
-    ASSERT_FALSE(observer.closed);
     DnsTlsSessionCache cache;
-    auto socket = std::make_unique<DnsTlsSocket>(server, MARK, &observer, &cache);
+};
+
+TEST_F(DnsTlsSocketTest, SlowDestructor) {
+    ASSERT_TRUE(tls.startServer());
+
+    MockDnsTlsSocketObserver observer;
+    auto socket = makeDnsTlsSocket(&observer);
+
     ASSERT_TRUE(socket->initialize());
+    ASSERT_TRUE(socket->startHandshake());
 
     // Test: Time the socket destructor.  This should be fast.
     auto before = std::chrono::steady_clock::now();
+    EXPECT_CALL(observer, onClosed);
     socket.reset();
     auto after = std::chrono::steady_clock::now();
     auto delay = after - before;
     LOG(DEBUG) << "Shutdown took " << delay / std::chrono::nanoseconds{1} << "ns";
-    EXPECT_TRUE(observer.closed);
     // Shutdown should complete in milliseconds, but if the shutdown signal is lost
     // it will wait for the timeout, which is expected to take 20seconds.
     EXPECT_LT(delay, std::chrono::seconds{5});
 }
 
+TEST_F(DnsTlsSocketTest, StartHandshake) {
+    ASSERT_TRUE(tls.startServer());
+
+    MockDnsTlsSocketObserver observer;
+    auto socket = makeDnsTlsSocket(&observer);
+
+    // Call the function before the call to initialize().
+    EXPECT_FALSE(socket->startHandshake());
+
+    // Call the function after the call to initialize().
+    EXPECT_TRUE(socket->initialize());
+    EXPECT_TRUE(socket->startHandshake());
+
+    // Call both of them again.
+    EXPECT_FALSE(socket->initialize());
+    EXPECT_FALSE(socket->startHandshake());
+
+    // Should happen when joining the loop thread in |socket| destruction.
+    EXPECT_CALL(observer, onClosed);
+}
+
+TEST_F(DnsTlsSocketTest, ShutdownSignal) {
+    ASSERT_TRUE(tls.startServer());
+
+    MockDnsTlsSocketObserver observer;
+    std::unique_ptr<DnsTlsSocket> socket;
+
+    const auto setupAndStartHandshake = [&]() {
+        socket = makeDnsTlsSocket(&observer);
+        EXPECT_TRUE(socket->initialize());
+        enableAsyncHandshake(socket);
+        EXPECT_TRUE(socket->startHandshake());
+    };
+    const auto triggerShutdown = [&](const std::string& traceLog) {
+        SCOPED_TRACE(traceLog);
+        auto before = std::chrono::steady_clock::now();
+        EXPECT_CALL(observer, onClosed);
+        socket.reset();
+        auto after = std::chrono::steady_clock::now();
+        auto delay = after - before;
+        LOG(INFO) << "Shutdown took " << delay / std::chrono::nanoseconds{1} << "ns";
+        EXPECT_LT(delay, std::chrono::seconds{1});
+    };
+
+    tls.setHangOnHandshakeForTesting(true);
+
+    // Test 1: Reset the DnsTlsSocket which is doing the handshake.
+    setupAndStartHandshake();
+    triggerShutdown("Shutdown handshake w/o query requests");
+
+    // Test 2: Reset the DnsTlsSocket which is doing the handshake with some query requests.
+    setupAndStartHandshake();
+
+    // DnsTlsSocket doesn't report the status of pending queries. The decision whether to mark
+    // a query request as failed or not is made in DnsTlsTransport.
+    EXPECT_CALL(observer, onResponse).Times(0);
+    EXPECT_TRUE(socket->query(1, makeSlice(QUERY)));
+    EXPECT_TRUE(socket->query(2, makeSlice(QUERY)));
+    triggerShutdown("Shutdown handshake w/ query requests");
+}
+
 } // end of namespace net
 } // end of namespace android
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 120000
index 0000000..475ba8f
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1 @@
+../../../build/soong/scripts/rustfmt.toml
\ No newline at end of file
diff --git a/tests/Android.bp b/tests/Android.bp
index 5dcf89b..66c8632 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -102,7 +102,7 @@
         "libnetdutils",
         "libprotobuf-cpp-lite",
         "netd_aidl_interface-ndk_platform",
-        "netd_event_listener_interface-ndk_platform",
+        "netd_event_listener_interface-unstable-ndk_platform",
         "server_configurable_flags",
         "stats_proto",
     ],
@@ -135,7 +135,7 @@
         "libnetd_test_resolv_utils",
         "libnetdutils",
         "libutils",
-        "netd_event_listener_interface-ndk_platform",
+        "netd_event_listener_interface-unstable-ndk_platform",
         "netd_aidl_interface-ndk_platform",
     ],
 }
@@ -165,7 +165,6 @@
     ],
     static_libs: [
         "dnsresolver_aidl_interface-unstable-ndk_platform",
-        "libbpf_android",
         "libcrypto_static",
         "libgmock",
         "libnetd_test_dnsresponder_ndk",
@@ -175,7 +174,7 @@
         "libssl",
         "libutils",
         "netd_aidl_interface-ndk_platform",
-        "netd_event_listener_interface-ndk_platform",
+        "netd_event_listener_interface-unstable-ndk_platform",
         "libipchecksum",
     ],
     // This test talks to the DnsResolver module over a binary protocol on a socket, so keep it as
diff --git a/tests/dns_metrics_listener/Android.bp b/tests/dns_metrics_listener/Android.bp
index 74c545e..0122dc0 100644
--- a/tests/dns_metrics_listener/Android.bp
+++ b/tests/dns_metrics_listener/Android.bp
@@ -11,6 +11,6 @@
     ],
     static_libs: [
         "libutils",
-        "netd_event_listener_interface-ndk_platform",
+        "netd_event_listener_interface-unstable-ndk_platform",
     ],
 }
diff --git a/tests/dns_metrics_listener/base_metrics_listener.cpp b/tests/dns_metrics_listener/base_metrics_listener.cpp
index c46f093..e0dd972 100644
--- a/tests/dns_metrics_listener/base_metrics_listener.cpp
+++ b/tests/dns_metrics_listener/base_metrics_listener.cpp
@@ -45,7 +45,12 @@
 
 ::ndk::ScopedAStatus BaseMetricsListener::onWakeupEvent(
         const std::string& /*prefix*/, int32_t /*uid*/, int32_t /*ethertype*/,
+// TODO: remove build flag when mainline release branch migrates to Android S (b/168163123).
+#ifdef __ANDROID_API_S__
+        int32_t /*ipNextHeader*/, const std::vector<uint8_t>& /*dstHw*/,
+#else
         int32_t /*ipNextHeader*/, const std::vector<int8_t>& /*dstHw*/,
+#endif
         const std::string& /*srcIp*/, const std::string& /*dstIp*/, int32_t /*srcPort*/,
         int32_t /*dstPort*/, int64_t /*timestampNs*/) {
     // default no-op
diff --git a/tests/dns_metrics_listener/base_metrics_listener.h b/tests/dns_metrics_listener/base_metrics_listener.h
index d50997d..61a3270 100644
--- a/tests/dns_metrics_listener/base_metrics_listener.h
+++ b/tests/dns_metrics_listener/base_metrics_listener.h
@@ -45,7 +45,12 @@
                                                 int32_t /*uid*/) override;
     virtual ::ndk::ScopedAStatus onWakeupEvent(const std::string& /*prefix*/, int32_t /*uid*/,
                                                int32_t /*ethertype*/, int32_t /*ipNextHeader*/,
+// TODO: remove build flag when mainline release branch migrates to Android S (b/168163123).
+#ifdef __ANDROID_API_S__
+                                               const std::vector<uint8_t>& /*dstHw*/,
+#else
                                                const std::vector<int8_t>& /*dstHw*/,
+#endif
                                                const std::string& /*srcIp*/,
                                                const std::string& /*dstIp*/, int32_t /*srcPort*/,
                                                int32_t /*dstPort*/,
diff --git a/tests/dns_responder/Android.bp b/tests/dns_responder/Android.bp
index 832ac88..855e72b 100644
--- a/tests/dns_responder/Android.bp
+++ b/tests/dns_responder/Android.bp
@@ -18,7 +18,7 @@
         "libnetdutils",
         "libssl",
         "netd_aidl_interface-ndk_platform",
-        "netd_event_listener_interface-ndk_platform",
+        "netd_event_listener_interface-unstable-ndk_platform",
     ],
     srcs: [
         "dns_responder.cpp",
diff --git a/tests/dns_responder/dns_tls_frontend.cpp b/tests/dns_responder/dns_tls_frontend.cpp
index d960254..3921297 100644
--- a/tests/dns_responder/dns_tls_frontend.cpp
+++ b/tests/dns_responder/dns_tls_frontend.cpp
@@ -114,7 +114,6 @@
             PLOG(INFO) << "ignore creating socket failed " << s.get();
             continue;
         }
-        enableSockopt(s.get(), SOL_SOCKET, SO_REUSEPORT).ignoreError();
         enableSockopt(s.get(), SOL_SOCKET, SO_REUSEADDR).ignoreError();
         std::string host_str = addr2str(ai->ai_addr, ai->ai_addrlen);
         if (bind(s.get(), ai->ai_addr, ai->ai_addrlen)) {
@@ -223,6 +222,11 @@
                 // client, including cleanup actions.
                 queries_ += handleRequests(ssl.get(), client.get());
             }
+
+            if (passiveClose_) {
+                LOG(DEBUG) << "hold the current connection until next connection request";
+                clientFd = std::move(client);
+            }
         }
     }
     LOG(DEBUG) << "Ending loop";
@@ -230,6 +234,7 @@
 
 int DnsTlsFrontend::handleRequests(SSL* ssl, int clientFd) {
     int queryCounts = 0;
+    std::vector<uint8_t> reply;
     pollfd fds = {.fd = clientFd, .events = POLLIN};
     do {
         uint8_t queryHeader[2];
@@ -263,16 +268,25 @@
         uint8_t responseHeader[2];
         responseHeader[0] = rlen >> 8;
         responseHeader[1] = rlen;
-        if (SSL_write(ssl, responseHeader, 2) != 2) {
-            LOG(INFO) << "Failed to write response header";
-            return queryCounts;
-        }
-        if (SSL_write(ssl, recv_buffer, rlen) != rlen) {
-            LOG(INFO) << "Failed to write response body";
-            return queryCounts;
-        }
+        reply.insert(reply.end(), responseHeader, responseHeader + 2);
+        reply.insert(reply.end(), recv_buffer, recv_buffer + rlen);
+
         ++queryCounts;
-    } while (poll(&fds, 1, 1) > 0);
+        if (queryCounts >= delayQueries_) {
+            break;
+        }
+    } while (poll(&fds, 1, delayQueriesTimeout_) > 0);
+
+    if (queryCounts < delayQueries_) {
+        LOG(WARNING) << "Expect " << delayQueries_ << " queries, but actually received "
+                     << queryCounts << " queries";
+    }
+
+    const int replyLen = reply.size();
+    LOG(DEBUG) << "Sending " << queryCounts << "queries at once, byte = " << replyLen;
+    if (SSL_write(ssl, reply.data(), replyLen) != replyLen) {
+        LOG(WARNING) << "Failed to write response body";
+    }
 
     LOG(DEBUG) << __func__ << " return: " << queryCounts;
     return queryCounts;
diff --git a/tests/dns_responder/dns_tls_frontend.h b/tests/dns_responder/dns_tls_frontend.h
index 6ba5681..02cdc21 100644
--- a/tests/dns_responder/dns_tls_frontend.h
+++ b/tests/dns_responder/dns_tls_frontend.h
@@ -62,6 +62,12 @@
     void set_chain_length(int length) { chain_length_ = length; }
     void setHangOnHandshakeForTesting(bool hangOnHandshake) { hangOnHandshake_ = hangOnHandshake; }
 
+    // Set DnsTlsFrontend to not reply any response until there are |delay| responses or timeout.
+    void setDelayQueries(int delay) { delayQueries_ = delay; }
+    void setDelayQueriesTimeout(int timeout) { delayQueriesTimeout_ = timeout; }
+
+    void setPassiveClose(bool passiveClose) { passiveClose_ = passiveClose; }
+
     static constexpr char kDefaultListenAddr[] = "127.0.0.3";
     static constexpr char kDefaultListenService[] = "853";
     static constexpr char kDefaultBackendAddr[] = "127.0.0.3";
@@ -94,6 +100,9 @@
     std::mutex update_mutex_;
     int chain_length_ = 1;
     std::atomic<bool> hangOnHandshake_ = false;
+    std::atomic<int> delayQueries_ = 1;
+    std::atomic<int> delayQueriesTimeout_ = 1;
+    std::atomic<bool> passiveClose_ = false;
 };
 
 }  // namespace test
diff --git a/tests/dnsresolver_binder_test.cpp b/tests/dnsresolver_binder_test.cpp
index 6fb73f3..0ad9b24 100644
--- a/tests/dnsresolver_binder_test.cpp
+++ b/tests/dnsresolver_binder_test.cpp
@@ -21,12 +21,17 @@
 #include <netdb.h>
 
 #include <iostream>
+#include <regex>
+#include <string>
+#include <thread>
 #include <vector>
 
 #include <aidl/android/net/IDnsResolver.h>
 #include <android-base/file.h>
+#include <android-base/format.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <android-base/unique_fd.h>
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
 #include <gmock/gmock-matchers.h>
@@ -41,9 +46,13 @@
 #include "dns_responder_client_ndk.h"
 
 using aidl::android::net::IDnsResolver;
+using aidl::android::net::ResolverHostsParcel;
+using aidl::android::net::ResolverOptionsParcel;
 using aidl::android::net::ResolverParamsParcel;
 using aidl::android::net::metrics::INetdEventListener;
+using android::base::ReadFdToString;
 using android::base::StringPrintf;
+using android::base::unique_fd;
 using android::net::ResolverStats;
 using android::net::metrics::TestOnDnsEvent;
 using android::netdutils::Stopwatch;
@@ -52,6 +61,37 @@
 // Sync from TEST_NETID in dns_responder_client.cpp as resolv_integration_test.cpp does.
 constexpr int TEST_NETID = 30;
 
+namespace {
+
+std::vector<std::string> dumpService(ndk::SpAIBinder binder) {
+    unique_fd localFd, remoteFd;
+    bool success = Pipe(&localFd, &remoteFd);
+    EXPECT_TRUE(success) << "Failed to open pipe for dumping: " << strerror(errno);
+    if (!success) return {};
+
+    // dump() blocks until another thread has consumed all its output.
+    std::thread dumpThread = std::thread([binder, remoteFd{std::move(remoteFd)}]() {
+        EXPECT_EQ(STATUS_OK, AIBinder_dump(binder.get(), remoteFd, nullptr, 0));
+    });
+
+    std::string dumpContent;
+
+    EXPECT_TRUE(ReadFdToString(localFd.get(), &dumpContent))
+            << "Error during dump: " << strerror(errno);
+    dumpThread.join();
+
+    std::stringstream dumpStream(std::move(dumpContent));
+    std::vector<std::string> lines;
+    std::string line;
+    while (std::getline(dumpStream, line)) {
+        lines.push_back(std::move(line));
+    }
+
+    return lines;
+}
+
+}  // namespace
+
 class DnsResolverBinderTest : public ::testing::Test {
   public:
     DnsResolverBinderTest() {
@@ -65,12 +105,144 @@
     }
 
     ~DnsResolverBinderTest() {
+        expectLog();
         // Destroy cache for test
         mDnsResolver->destroyNetworkCache(TEST_NETID);
     }
 
   protected:
+    void expectLog() {
+        ndk::SpAIBinder netdBinder = ndk::SpAIBinder(AServiceManager_getService("netd"));
+        // This could happen when the test isn't running as root, or if netd isn't running.
+        assert(nullptr != netdBinder.get());
+        // Send the service dump request to netd.
+        std::vector<std::string> lines = dumpService(netdBinder);
+
+        // Basic regexp to match dump output lines. Matches the beginning and end of the line, and
+        // puts the output of the command itself into the first match group.
+        // Example: "      11-05 00:23:39.481 myCommand(args) <2.02ms>".
+        // Note: There are 4 leading blank characters in Q, but 6 in R.
+        const std::basic_regex lineRegex(
+                "^ {4,6}[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[.][0-9]{3} "
+                "(.*)"
+                " <[0-9]+[.][0-9]{2}ms>$");
+
+        // For each element of testdata, check that the expected output appears in the dump output.
+        // If not, fail the test and use hintRegex to print similar lines to assist in debugging.
+        for (const auto& td : mExpectedLogData) {
+            const bool found =
+                    std::any_of(lines.begin(), lines.end(), [&](const std::string& line) {
+                        std::smatch match;
+                        if (!std::regex_match(line, match, lineRegex)) return false;
+                        return (match.size() == 2) && (match[1].str() == td.output);
+                    });
+            EXPECT_TRUE(found) << "Didn't find line '" << td.output << "' in dumpsys output.";
+            if (found) continue;
+            std::cerr << "Similar lines" << std::endl;
+            for (const auto& line : lines) {
+                if (std::regex_search(line, std::basic_regex(td.hintRegex))) {
+                    std::cerr << line << std::endl;
+                }
+            }
+        }
+
+        // The log output is different between R and S, either one is fine for the
+        // test to avoid test compatible issue.
+        // TODO: Remove after S.
+        for (const auto& td : mExpectedLogDataWithPacel) {
+            const bool found =
+                    std::any_of(lines.begin(), lines.end(), [&](const std::string& line) {
+                        std::smatch match;
+                        if (!std::regex_match(line, match, lineRegex)) return false;
+                        return (match.size() == 2) && ((match[1].str() == td.withPacel.output) ||
+                                                       (match[1].str() == td.withoutPacel.output));
+                    });
+            EXPECT_TRUE(found) << fmt::format("Didn't find line '{}' or '{}' in dumpsys output.",
+                                              td.withPacel.output, td.withoutPacel.output);
+            if (found) continue;
+            std::cerr << "Similar lines" << std::endl;
+            for (const auto& line : lines) {
+                if (std::regex_search(line, std::basic_regex(td.withPacel.hintRegex))) {
+                    std::cerr << line << std::endl;
+                }
+                if (std::regex_search(line, std::basic_regex(td.withoutPacel.hintRegex))) {
+                    std::cerr << line << std::endl;
+                }
+            }
+        }
+    }
+
+    struct LogData {
+        // Expected contents of the dump command.
+        const std::string output;
+        // A regex that might be helpful in matching relevant lines in the output.
+        // Used to make it easier to add test cases for this code.
+        const std::string hintRegex;
+    };
+
+    // TODO: Remove this struct and below toString methods after S.
+    struct PossibleLogData {
+        LogData withPacel;
+        LogData withoutPacel;
+    };
+
+    std::string toString(const std::vector<ResolverHostsParcel>& parms) {
+        std::string o;
+        const size_t size = parms.size();
+        for (size_t i = 0; i < size; ++i) {
+            o.append(fmt::format("ResolverHostsParcel{{ipAddr: {}, hostName: {}}}", parms[i].ipAddr,
+                                 parms[i].hostName));
+            if (i + 1 < size) o.append(", ");
+        }
+        return o;
+    }
+
+    std::string toString(const ResolverOptionsParcel& parms) {
+        return fmt::format("ResolverOptionsParcel{{hosts: [{}], tcMode: {}, enforceDnsUid: {}}}",
+                           toString(parms.hosts), parms.tcMode, parms.enforceDnsUid);
+    }
+
+    std::string toString(const ResolverParamsParcel& parms) {
+        return fmt::format(
+                "ResolverParamsParcel{{netId: {}, sampleValiditySeconds: {}, successThreshold: {}, "
+                "minSamples: {}, "
+                "maxSamples: {}, baseTimeoutMsec: {}, retryCount: {}, "
+                "servers: [{}], domains: [{}], "
+                "tlsName: {}, tlsServers: [{}], "
+                "tlsFingerprints: [{}], "
+                "caCertificate: {}, tlsConnectTimeoutMs: {}, "
+                "resolverOptions: {}, transportTypes: [{}]}}",
+                parms.netId, parms.sampleValiditySeconds, parms.successThreshold, parms.minSamples,
+                parms.maxSamples, parms.baseTimeoutMsec, parms.retryCount,
+                fmt::join(parms.servers, ", "), fmt::join(parms.domains, ", "), parms.tlsName,
+                fmt::join(parms.tlsServers, ", "), fmt::join(parms.tlsFingerprints, ", "),
+                android::base::StringReplace(parms.caCertificate, "\n", "\\n", true),
+                parms.tlsConnectTimeoutMs, toString(parms.resolverOptions),
+                fmt::join(parms.transportTypes, ", "));
+    }
+
+    PossibleLogData toSetResolverConfigurationLogData(const ResolverParamsParcel& parms,
+                                                      int returnCode = 0) {
+        std::string outputWithParcel = "setResolverConfiguration(" + toString(parms) + ")";
+        std::string hintRegexWithParcel = fmt::format("setResolverConfiguration.*{}", parms.netId);
+
+        std::string outputWithoutParcel = "setResolverConfiguration()";
+        std::string hintRegexWithoutParcel = "setResolverConfiguration";
+        if (returnCode != 0) {
+            outputWithParcel.append(fmt::format(" -> ServiceSpecificException({}, \"{}\")",
+                                                returnCode, strerror(returnCode)));
+            hintRegexWithParcel.append(fmt::format(".*{}", returnCode));
+            outputWithoutParcel.append(fmt::format(" -> ServiceSpecificException({}, \"{}\")",
+                                                   returnCode, strerror(returnCode)));
+            hintRegexWithoutParcel.append(fmt::format(".*{}", returnCode));
+        }
+        return {{std::move(outputWithParcel), std::move(hintRegexWithParcel)},
+                {std::move(outputWithoutParcel), std::move(hintRegexWithoutParcel)}};
+    }
+
     std::shared_ptr<aidl::android::net::IDnsResolver> mDnsResolver;
+    std::vector<LogData> mExpectedLogData;
+    std::vector<PossibleLogData> mExpectedLogDataWithPacel;
 };
 
 class TimedOperation : public Stopwatch {
@@ -95,6 +267,9 @@
     ::ndk::ScopedAStatus status = mDnsResolver->registerEventListener(nullptr);
     ASSERT_FALSE(status.isOk());
     ASSERT_EQ(EINVAL, status.getServiceSpecificError());
+    mExpectedLogData.push_back(
+            {"registerEventListener() -> ServiceSpecificException(22, \"Invalid argument\")",
+             "registerEventListener.*22"});
 }
 
 TEST_F(DnsResolverBinderTest, RegisterEventListener_DuplicateSubscription) {
@@ -104,11 +279,15 @@
     std::shared_ptr<DummyListener> dummyListener = ndk::SharedRefBase::make<DummyListener>();
     ::ndk::ScopedAStatus status = mDnsResolver->registerEventListener(dummyListener);
     ASSERT_TRUE(status.isOk()) << status.getMessage();
+    mExpectedLogData.push_back({"registerEventListener()", "registerEventListener.*"});
 
     // Expect to subscribe failed with registered listener instance.
     status = mDnsResolver->registerEventListener(dummyListener);
     ASSERT_FALSE(status.isOk());
     ASSERT_EQ(EEXIST, status.getServiceSpecificError());
+    mExpectedLogData.push_back(
+            {"registerEventListener() -> ServiceSpecificException(17, \"File exists\")",
+             "registerEventListener.*17"});
 }
 
 // TODO: Move this test to resolv_integration_test.cpp
@@ -161,6 +340,7 @@
             ndk::SharedRefBase::make<TestOnDnsEvent>(expectedResults);
     ::ndk::ScopedAStatus status = mDnsResolver->registerEventListener(testOnDnsEvent);
     ASSERT_TRUE(status.isOk()) << status.getMessage();
+    mExpectedLogData.push_back({"registerEventListener()", "registerEventListener.*"});
 
     // DNS queries.
     // Once all expected events of expectedResults are received by the listener, the unit test will
@@ -238,10 +418,13 @@
             SCOPED_TRACE(StringPrintf("test case %zu should have passed", i));
             SCOPED_TRACE(status.getMessage());
             EXPECT_EQ(0, status.getServiceSpecificError());
+            mExpectedLogDataWithPacel.push_back(toSetResolverConfigurationLogData(resolverParams));
         } else {
             SCOPED_TRACE(StringPrintf("test case %zu should have failed", i));
             EXPECT_EQ(EX_SERVICE_SPECIFIC, status.getExceptionCode());
             EXPECT_EQ(td.expectedReturnCode, status.getServiceSpecificError());
+            mExpectedLogDataWithPacel.push_back(
+                    toSetResolverConfigurationLogData(resolverParams, td.expectedReturnCode));
         }
     }
 }
@@ -252,6 +435,7 @@
     resolverParams.transportTypes = {IDnsResolver::TRANSPORT_WIFI, IDnsResolver::TRANSPORT_VPN};
     ::ndk::ScopedAStatus status = mDnsResolver->setResolverConfiguration(resolverParams);
     EXPECT_TRUE(status.isOk()) << status.getMessage();
+    mExpectedLogDataWithPacel.push_back(toSetResolverConfigurationLogData(resolverParams));
     // TODO: Find a way to fix a potential deadlock here if it's larger than pipe buffer
     // size(65535).
     android::base::unique_fd writeFd, readFd;
@@ -268,6 +452,7 @@
     auto resolverParams = DnsResponderClient::GetDefaultResolverParamsParcel();
     ::ndk::ScopedAStatus status = mDnsResolver->setResolverConfiguration(resolverParams);
     EXPECT_TRUE(status.isOk()) << status.getMessage();
+    mExpectedLogDataWithPacel.push_back(toSetResolverConfigurationLogData(resolverParams));
     android::base::unique_fd writeFd, readFd;
     EXPECT_TRUE(Pipe(&readFd, &writeFd));
     EXPECT_EQ(mDnsResolver->dump(writeFd.get(), nullptr, 0), 0);
@@ -291,6 +476,7 @@
             TEST_NETID, testParams, servers, domains, "", {});
     ::ndk::ScopedAStatus status = mDnsResolver->setResolverConfiguration(resolverParams);
     EXPECT_TRUE(status.isOk()) << status.getMessage();
+    mExpectedLogDataWithPacel.push_back(toSetResolverConfigurationLogData(resolverParams));
 
     std::vector<std::string> res_servers;
     std::vector<std::string> res_domains;
@@ -335,46 +521,68 @@
 
     // Create a new network cache.
     EXPECT_TRUE(mDnsResolver->createNetworkCache(ANOTHER_TEST_NETID).isOk());
+    mExpectedLogData.push_back({"createNetworkCache(31)", "createNetworkCache.*31"});
 
     // create it again, expect a EEXIST.
     EXPECT_EQ(EEXIST,
               mDnsResolver->createNetworkCache(ANOTHER_TEST_NETID).getServiceSpecificError());
+    mExpectedLogData.push_back(
+            {"createNetworkCache(31) -> ServiceSpecificException(17, \"File exists\")",
+             "createNetworkCache.*31.*17"});
 
     // destroy it.
     EXPECT_TRUE(mDnsResolver->destroyNetworkCache(ANOTHER_TEST_NETID).isOk());
+    mExpectedLogData.push_back({"destroyNetworkCache(31)", "destroyNetworkCache.*31"});
 
     // re-create it
     EXPECT_TRUE(mDnsResolver->createNetworkCache(ANOTHER_TEST_NETID).isOk());
+    mExpectedLogData.push_back({"createNetworkCache(31)", "createNetworkCache.*31"});
 
     // destroy it.
     EXPECT_TRUE(mDnsResolver->destroyNetworkCache(ANOTHER_TEST_NETID).isOk());
+    mExpectedLogData.push_back({"destroyNetworkCache(31)", "destroyNetworkCache.*31"});
 
     // re-destroy it
     EXPECT_TRUE(mDnsResolver->destroyNetworkCache(ANOTHER_TEST_NETID).isOk());
+    mExpectedLogData.push_back({"destroyNetworkCache(31)", "destroyNetworkCache.*31"});
 }
 
 TEST_F(DnsResolverBinderTest, FlushNetworkCache) {
     SKIP_IF_REMOTE_VERSION_LESS_THAN(mDnsResolver.get(), 4);
     // cache has beed created in DnsResolverBinderTest constructor
     EXPECT_TRUE(mDnsResolver->flushNetworkCache(TEST_NETID).isOk());
+    mExpectedLogData.push_back({"flushNetworkCache(30)", "destroyNetworkCache.*30"});
     EXPECT_EQ(ENONET, mDnsResolver->flushNetworkCache(-1).getServiceSpecificError());
+    mExpectedLogData.push_back(
+            {"flushNetworkCache(-1) -> ServiceSpecificException(64, \"Machine is not on the "
+             "network\")",
+             "flushNetworkCache.*-1.*64"});
 }
 
 TEST_F(DnsResolverBinderTest, setLogSeverity) {
     // Expect fail
     EXPECT_EQ(EINVAL, mDnsResolver->setLogSeverity(-1).getServiceSpecificError());
+    mExpectedLogData.push_back(
+            {"setLogSeverity(-1) -> ServiceSpecificException(22, \"Invalid argument\")",
+             "flushNetworkCache.*-1.*22"});
 
     // Test set different log level
     EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_VERBOSE).isOk());
+    mExpectedLogData.push_back({"setLogSeverity(0)", "setLogSeverity.*0"});
 
     EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_DEBUG).isOk());
+    mExpectedLogData.push_back({"setLogSeverity(1)", "setLogSeverity.*1"});
 
     EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_INFO).isOk());
+    mExpectedLogData.push_back({"setLogSeverity(2)", "setLogSeverity.*2"});
 
     EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_WARNING).isOk());
+    mExpectedLogData.push_back({"setLogSeverity(3)", "setLogSeverity.*3"});
 
     EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_ERROR).isOk());
+    mExpectedLogData.push_back({"setLogSeverity(4)", "setLogSeverity.*4"});
 
     // Set back to default
     EXPECT_TRUE(mDnsResolver->setLogSeverity(IDnsResolver::DNS_RESOLVER_LOG_WARNING).isOk());
+    mExpectedLogData.push_back({"setLogSeverity(3)", "setLogSeverity.*3"});
 }
diff --git a/tests/resolv_gold_test.cpp b/tests/resolv_gold_test.cpp
index e8df5f6..c5b24aa 100644
--- a/tests/resolv_gold_test.cpp
+++ b/tests/resolv_gold_test.cpp
@@ -368,6 +368,8 @@
     ASSERT_TRUE(tls.startServer());
     ASSERT_NO_FATAL_FAILURE(SetResolversWithTls());
     EXPECT_TRUE(WaitForPrivateDnsValidation(tls.listen_address()));
+    tls.setDelayQueries(2);
+    tls.setDelayQueriesTimeout(200);
 
     dns.clearQueries();
     addrinfo* res = nullptr;
@@ -429,6 +431,8 @@
     test::DNSResponder dns(test::DNSResponder::MappingType::BINARY_PACKET);
     ASSERT_TRUE(dns.startServer());
     test::DnsTlsFrontend tls;
+    tls.setDelayQueries(2);
+    tls.setDelayQueriesTimeout(200);
 
     if (protocol == DnsProtocol::CLEARTEXT) {
         ASSERT_NO_FATAL_FAILURE(SetResolvers());
diff --git a/tests/resolv_integration_test.cpp b/tests/resolv_integration_test.cpp
index e347126..01ed421 100644
--- a/tests/resolv_integration_test.cpp
+++ b/tests/resolv_integration_test.cpp
@@ -27,7 +27,6 @@
 #include <arpa/inet.h>
 #include <arpa/nameser.h>
 #include <binder/ProcessState.h>
-#include <bpf/BpfUtils.h>
 #include <cutils/sockets.h>
 #include <gmock/gmock-matchers.h>
 #include <gtest/gtest.h>
@@ -77,6 +76,12 @@
 constexpr int TEST_VPN_NETID = 65502;
 constexpr int MAXPACKET = (8 * 1024);
 
+const std::string kSortNameserversFlag("persist.device_config.netd_native.sort_nameservers");
+const std::string kDotConnectTimeoutMsFlag(
+        "persist.device_config.netd_native.dot_connect_timeout_ms");
+const std::string kDotAsyncHandshakeFlag("persist.device_config.netd_native.dot_async_handshake");
+const std::string kDotMaxretriesFlag("persist.device_config.netd_native.dot_maxtries");
+
 // Semi-public Bionic hook used by the NDK (frameworks/base/native/android/net.c)
 // Tested here for convenience.
 extern "C" int android_getaddrinfofornet(const char* hostname, const char* servname,
@@ -178,8 +183,8 @@
         // service.
 
         AIBinder* binder = AServiceManager_getService("dnsresolver");
-        ndk::SpAIBinder resolvBinder = ndk::SpAIBinder(binder);
-        auto resolvService = aidl::android::net::IDnsResolver::fromBinder(resolvBinder);
+        sResolvBinder = ndk::SpAIBinder(binder);
+        auto resolvService = aidl::android::net::IDnsResolver::fromBinder(sResolvBinder);
         ASSERT_NE(nullptr, resolvService.get());
 
         // Subscribe the death recipient to the service IDnsResolver for detecting Netd death.
@@ -365,11 +370,16 @@
     // Use a shared static death recipient to monitor the service death. The static death
     // recipient could monitor the death not only during the test but also between tests.
     static AIBinder_DeathRecipient* sResolvDeathRecipient;  // Initialized in SetUpTestSuite.
+
+    // The linked AIBinder_DeathRecipient will be automatically unlinked if the binder is deleted.
+    // The binder needs to be retained throughout tests.
+    static ndk::SpAIBinder sResolvBinder;
 };
 
 // Initialize static member of class.
 std::shared_ptr<DnsMetricsListener> ResolverTest::sDnsMetricsListener;
 AIBinder_DeathRecipient* ResolverTest::sResolvDeathRecipient;
+ndk::SpAIBinder ResolverTest::sResolvBinder;
 
 TEST_F(ResolverTest, GetHostByName) {
     constexpr char nonexistent_host_name[] = "nonexistent.example.com.";
@@ -382,7 +392,7 @@
     result = gethostbyname("nonexistent");
     EXPECT_EQ(1U, GetNumQueriesForType(dns, ns_type::ns_t_a, nonexistent_host_name));
     ASSERT_TRUE(result == nullptr);
-    ASSERT_EQ(HOST_NOT_FOUND, h_errno);
+    EXPECT_EQ(HOST_NOT_FOUND, h_errno);
 
     dns.clearQueries();
     result = gethostbyname("hello");
@@ -394,6 +404,19 @@
     EXPECT_TRUE(result->h_addr_list[1] == nullptr);
 }
 
+TEST_F(ResolverTest, GetHostByName_NULL) {
+    // Most libc implementations would just crash on gethostbyname(NULL). Instead, Bionic
+    // serializes the null argument over dnsproxyd, causing the server-side to crash!
+    // This is a regression test.
+    const char* const testcases[] = {nullptr, "", "^"};
+    for (const char* name : testcases) {
+        SCOPED_TRACE(fmt::format("gethostbyname({})", name ? name : "NULL"));
+        const hostent* result = gethostbyname(name);
+        EXPECT_TRUE(result == nullptr);
+        EXPECT_EQ(HOST_NOT_FOUND, h_errno);
+    }
+}
+
 TEST_F(ResolverTest, GetHostByName_cnames) {
     constexpr char host_name[] = "host.example.com.";
     size_t cnamecount = 0;
@@ -893,6 +916,51 @@
     EXPECT_TRUE(result == nullptr);
 }
 
+TEST_F(ResolverTest, GetAddrInfoForCaseInSensitiveDomains) {
+    test::DNSResponder dns;
+    const char* host_name = "howdy.example.com.";
+    const char* host_name2 = "HOWDY.example.com.";
+    const std::vector<DnsRecord> records = {
+            {host_name, ns_type::ns_t_a, "1.2.3.4"},
+            {host_name, ns_type::ns_t_aaaa, "::1.2.3.4"},
+            {host_name2, ns_type::ns_t_a, "1.2.3.5"},
+            {host_name2, ns_type::ns_t_aaaa, "::1.2.3.5"},
+    };
+    StartDns(dns, records);
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+
+    ScopedAddrinfo hostname_result = safe_getaddrinfo("howdy", nullptr, nullptr);
+    EXPECT_TRUE(hostname_result != nullptr);
+    const size_t hostname1_count_after_first_query = GetNumQueries(dns, host_name);
+    EXPECT_LE(1U, hostname1_count_after_first_query);
+    // Could be A or AAAA
+    std::string hostname_result_str = ToString(hostname_result);
+    EXPECT_TRUE(hostname_result_str == "1.2.3.4" || hostname_result_str == "::1.2.3.4");
+
+    // Verify that the name is cached.
+    ScopedAddrinfo hostname2_result = safe_getaddrinfo("HOWDY", nullptr, nullptr);
+    EXPECT_TRUE(hostname2_result != nullptr);
+    const size_t hostname1_count_after_second_query = GetNumQueries(dns, host_name);
+    EXPECT_LE(1U, hostname1_count_after_second_query);
+
+    // verify that there is no change in num of queries for howdy.example.com
+    EXPECT_EQ(hostname1_count_after_first_query, hostname1_count_after_second_query);
+
+    // Number of queries for HOWDY.example.com would be >= 1 if domain names
+    // are considered case-sensitive, else number of queries should be 0.
+    const size_t hostname2_count = GetNumQueries(dns, host_name2);
+    EXPECT_EQ(0U,hostname2_count);
+    std::string hostname2_result_str = ToString(hostname2_result);
+    EXPECT_TRUE(hostname2_result_str == "1.2.3.4" || hostname2_result_str == "::1.2.3.4");
+
+    // verify that the result is still the same address even though
+    // mixed-case string is not in the DNS
+    ScopedAddrinfo result = safe_getaddrinfo("HowDY", nullptr, nullptr);
+    EXPECT_TRUE(result != nullptr);
+    std::string result_str = ToString(result);
+    EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4");
+}
+
 TEST_F(ResolverTest, MultidomainResolution) {
     constexpr char host_name[] = "nihao.example2.com.";
     std::vector<std::string> searchDomains = {"example1.com", "example2.com", "example3.com"};
@@ -1081,7 +1149,6 @@
 }
 
 TEST_F(ResolverTest, SkipBadServersDueToInternalError) {
-    const std::string kSortNameserversFlag("persist.device_config.netd_native.sort_nameservers");
     constexpr char listen_addr1[] = "fe80::1";
     constexpr char listen_addr2[] = "255.255.255.255";
     constexpr char listen_addr3[] = "127.0.0.3";
@@ -1101,6 +1168,9 @@
         SCOPED_TRACE(fmt::format("sortNameversFlag_{}", sortNameserversFlag));
         ScopedSystemProperties scopedSystemProperties(kSortNameserversFlag, sortNameserversFlag);
 
+        // Re-setup test network to make experiment flag take effect.
+        resetNetwork();
+
         ASSERT_TRUE(mDnsClient.SetResolversFromParcel(setupParams));
 
         // Start sending synchronized querying.
@@ -1129,7 +1199,6 @@
 }
 
 TEST_F(ResolverTest, SkipBadServersDueToTimeout) {
-    const std::string kSortNameserversFlag("persist.device_config.netd_native.sort_nameservers");
     constexpr char listen_addr1[] = "127.0.0.3";
     constexpr char listen_addr2[] = "127.0.0.4";
     int counter = 0;  // To generate unique hostnames.
@@ -1153,6 +1222,9 @@
         SCOPED_TRACE(fmt::format("sortNameversFlag_{}", sortNameserversFlag));
         ScopedSystemProperties scopedSystemProperties(kSortNameserversFlag, sortNameserversFlag);
 
+        // Re-setup test network to make experiment flag take effect.
+        resetNetwork();
+
         ASSERT_TRUE(mDnsClient.SetResolversFromParcel(setupParams));
 
         // Start sending synchronized querying.
@@ -2050,12 +2122,7 @@
     ret = poll(wait_fd, 1, -1);
     revents = wait_fd[0].revents;
     if (revents & POLLIN) {
-        int n = resNetworkResult(fd, rcode, buf, bufLen);
-        // Verify that resNetworkResult() closed the fd
-        char unused;
-        EXPECT_EQ(-1, read(fd, &unused, sizeof unused));
-        EXPECT_EQ(EBADF, errno);
-        return n;
+        return resNetworkResult(fd, rcode, buf, bufLen);
     }
     return -1;
 }
@@ -4093,10 +4160,6 @@
 }
 
 TEST_F(ResolverTest, BlockDnsQueryWithUidRule) {
-    // This test relies on blocking traffic on loopback, which xt_qtaguid does not do.
-    // See aosp/358413 and b/34444781 for why.
-    SKIP_IF_BPF_NOT_SUPPORTED;
-
     constexpr char listen_addr1[] = "127.0.0.4";
     constexpr char listen_addr2[] = "::1";
     constexpr char host_name[] = "howdy.example.com.";
@@ -4144,8 +4207,6 @@
 }
 
 TEST_F(ResolverTest, EnforceDnsUid) {
-    SKIP_IF_BPF_NOT_SUPPORTED;
-
     constexpr char listen_addr1[] = "127.0.0.4";
     constexpr char listen_addr2[] = "::1";
     constexpr char host_name[] = "howdy.example.com.";
@@ -4217,9 +4278,6 @@
 }
 
 TEST_F(ResolverTest, ConnectTlsServerTimeout) {
-    const std::string kDotConnectTimeoutMsFlag(
-            "persist.device_config.netd_native.dot_connect_timeout_ms");
-    constexpr int expectedTimeout = 1000;
     constexpr char hostname1[] = "query1.example.com.";
     constexpr char hostname2[] = "query2.example.com.";
     const std::vector<DnsRecord> records = {
@@ -4227,53 +4285,177 @@
             {hostname2, ns_type::ns_t_a, "1.2.3.5"},
     };
 
-    test::DNSResponder dns;
-    StartDns(dns, records);
-    test::DnsTlsFrontend tls;
-    ASSERT_TRUE(tls.startServer());
-
     // The resolver will adjust the timeout value to 1000ms since the value is too small.
     ScopedSystemProperties scopedSystemProperties(kDotConnectTimeoutMsFlag, "100");
 
-    // Set up resolver to opportunistic mode with the default configuration.
-    const ResolverParamsParcel parcel = DnsResponderClient::GetDefaultResolverParamsParcel();
-    ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel));
-    EXPECT_TRUE(WaitForPrivateDnsValidation(tls.listen_address(), true));
-    EXPECT_TRUE(tls.waitForQueries(1));
-    tls.clearQueries();
-    dns.clearQueries();
+    static const struct TestConfig {
+        bool asyncHandshake;
+        int maxRetries;
 
-    // The server becomes unresponsive to the handshake request.
-    tls.setHangOnHandshakeForTesting(true);
+        // if asyncHandshake:
+        //   expectedTimeout = dotConnectTimeoutMs * maxRetries
+        // otherwise:
+        //   expectedTimeout = dotConnectTimeoutMs
+        int expectedTimeout;
+    } testConfigs[] = {
+            // Test mis-configured dot_maxtries flag.
+            {false, 0, 1000}, {true, 0, 1000},
 
-    // Expect the things happening in getaddrinfo():
-    //   1. Connect to the private DNS server.
-    //   2. SSL handshake times out.
-    //   3. Fallback to UDP transport, and then get the answer.
-    const addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_DGRAM};
-    auto [result, timeTakenMs] = safe_getaddrinfo_time_taken(hostname1, nullptr, hints);
+            {false, 1, 1000}, {false, 3, 1000}, {true, 1, 1000}, {true, 3, 3000},
+    };
 
-    EXPECT_NE(nullptr, result);
-    EXPECT_EQ(0, tls.queries());
-    EXPECT_EQ(1U, GetNumQueries(dns, hostname1));
-    EXPECT_EQ(records.at(0).addr, ToString(result));
+    for (const auto& config : testConfigs) {
+        SCOPED_TRACE(fmt::format("testConfig: [{}, {}]", config.asyncHandshake, config.maxRetries));
 
-    // A loose upper bound is set by adding 2000ms buffer time. Theoretically, getaddrinfo()
-    // should just take a bit more than expetTimeout milliseconds.
-    EXPECT_GE(timeTakenMs, expectedTimeout);
-    EXPECT_LE(timeTakenMs, expectedTimeout + 2000);
+        // Because a DnsTlsTransport lasts at least 5 minutes in spite of network
+        // destroyed, let the resolver creates an unique DnsTlsTransport every time
+        // so that the DnsTlsTransport won't interfere the other tests.
+        const std::string addr = getUniqueIPv4Address();
+        test::DNSResponder dns(addr);
+        StartDns(dns, records);
+        test::DnsTlsFrontend tls(addr, "853", addr, "53");
+        ASSERT_TRUE(tls.startServer());
 
-    // Set the server to be responsive. Verify that the resolver will attempt to reconnect
-    // to the server and then get the result within the timeout.
-    tls.setHangOnHandshakeForTesting(false);
-    std::tie(result, timeTakenMs) = safe_getaddrinfo_time_taken(hostname2, nullptr, hints);
+        ScopedSystemProperties scopedSystemProperties1(kDotAsyncHandshakeFlag,
+                                                       config.asyncHandshake ? "1" : "0");
+        ScopedSystemProperties scopedSystemProperties2(kDotMaxretriesFlag,
+                                                       std::to_string(config.maxRetries));
+        resetNetwork();
 
-    EXPECT_NE(nullptr, result);
-    EXPECT_TRUE(tls.waitForQueries(1));
-    EXPECT_EQ(1U, GetNumQueries(dns, hostname2));
-    EXPECT_EQ(records.at(1).addr, ToString(result));
+        // Set up resolver to opportunistic mode.
+        auto parcel = DnsResponderClient::GetDefaultResolverParamsParcel();
+        parcel.servers = {addr};
+        parcel.tlsServers = {addr};
+        ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel));
+        EXPECT_TRUE(WaitForPrivateDnsValidation(tls.listen_address(), true));
+        EXPECT_TRUE(tls.waitForQueries(1));
+        tls.clearQueries();
+        dns.clearQueries();
 
-    EXPECT_LE(timeTakenMs, expectedTimeout);
+        // The server becomes unresponsive to the handshake request.
+        tls.setHangOnHandshakeForTesting(true);
+
+        // Expect the things happening in getaddrinfo():
+        //   1. Connect to the private DNS server.
+        //   2. SSL handshake times out.
+        //   3. Fallback to UDP transport, and then get the answer.
+        const addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_DGRAM};
+        auto [result, timeTakenMs] = safe_getaddrinfo_time_taken(hostname1, nullptr, hints);
+
+        EXPECT_NE(nullptr, result);
+        EXPECT_EQ(0, tls.queries());
+        EXPECT_EQ(1U, GetNumQueries(dns, hostname1));
+        EXPECT_EQ(records.at(0).addr, ToString(result));
+
+        // A loose upper bound is set by adding 1000ms buffer time. Theoretically, getaddrinfo()
+        // should just take a bit more than expetTimeout milliseconds.
+        EXPECT_GE(timeTakenMs, config.expectedTimeout);
+        EXPECT_LE(timeTakenMs, config.expectedTimeout + 1000);
+
+        // Set the server to be responsive. Verify that the resolver will attempt to reconnect
+        // to the server and then get the result within the timeout.
+        tls.setHangOnHandshakeForTesting(false);
+        std::tie(result, timeTakenMs) = safe_getaddrinfo_time_taken(hostname2, nullptr, hints);
+
+        EXPECT_NE(nullptr, result);
+        EXPECT_TRUE(tls.waitForQueries(1));
+        EXPECT_EQ(1U, GetNumQueries(dns, hostname2));
+        EXPECT_EQ(records.at(1).addr, ToString(result));
+
+        EXPECT_LE(timeTakenMs, 200);
+    }
+}
+
+TEST_F(ResolverTest, ConnectTlsServerTimeout_ConcurrentQueries) {
+    constexpr uint32_t cacheFlag = ANDROID_RESOLV_NO_CACHE_LOOKUP;
+    constexpr char hostname[] = "hello.example.com.";
+    const std::vector<DnsRecord> records = {
+            {hostname, ns_type::ns_t_a, "1.2.3.4"},
+    };
+
+    static const struct TestConfig {
+        bool asyncHandshake;
+        int dotConnectTimeoutMs;
+        int maxRetries;
+        int concurrency;
+
+        // if asyncHandshake:
+        //   expectedTimeout = dotConnectTimeoutMs * maxRetries
+        // otherwise:
+        //   expectedTimeout = dotConnectTimeoutMs * concurrency
+        int expectedTimeout;
+    } testConfigs[] = {
+            // clang-format off
+            {false, 1000, 1, 5, 5000},
+            {false, 1000, 3, 5, 5000},
+            {true, 1000, 1, 5, 1000},
+            {true, 2500, 1, 10, 2500},
+            {true, 1000, 3, 5, 3000},
+            // clang-format on
+    };
+
+    // Launch query threads. Expected behaviors are:
+    // - when dot_async_handshake is disabled, one of the query threads triggers a
+    //   handshake and then times out. Then same as another query thread, and so forth.
+    // - when dot_async_handshake is enabled, only one handshake is triggered, and then
+    //   all of the query threads time out at the same time.
+    for (const auto& config : testConfigs) {
+        ScopedSystemProperties scopedSystemProperties1(kDotConnectTimeoutMsFlag,
+                                                       std::to_string(config.dotConnectTimeoutMs));
+        ScopedSystemProperties scopedSystemProperties2(kDotAsyncHandshakeFlag,
+                                                       config.asyncHandshake ? "1" : "0");
+        ScopedSystemProperties scopedSystemProperties3(kDotMaxretriesFlag,
+                                                       std::to_string(config.maxRetries));
+        resetNetwork();
+
+        for (const auto& dnsMode : {"OPPORTUNISTIC", "STRICT"}) {
+            SCOPED_TRACE(fmt::format("testConfig: [{}, {}]", config.asyncHandshake, dnsMode));
+
+            // Because a DnsTlsTransport lasts at least 5 minutes in spite of network
+            // destroyed, let the resolver creates an unique DnsTlsTransport every time
+            // so that the DnsTlsTransport won't interfere the other tests.
+            const std::string addr = getUniqueIPv4Address();
+            test::DNSResponder dns(addr);
+            StartDns(dns, records);
+            test::DnsTlsFrontend tls(addr, "853", addr, "53");
+            ASSERT_TRUE(tls.startServer());
+
+            auto parcel = DnsResponderClient::GetDefaultResolverParamsParcel();
+            parcel.servers = {addr};
+            parcel.tlsServers = {addr};
+            if (dnsMode == "STRICT") parcel.tlsName = kDefaultPrivateDnsHostName;
+            ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel));
+            EXPECT_TRUE(WaitForPrivateDnsValidation(tls.listen_address(), true));
+            EXPECT_TRUE(tls.waitForQueries(1));
+
+            // The server becomes unresponsive to the handshake request.
+            tls.setHangOnHandshakeForTesting(true);
+
+            Stopwatch s;
+            std::vector<std::thread> threads(config.concurrency);
+            for (std::thread& thread : threads) {
+                thread = std::thread([&]() {
+                    int fd = resNetworkQuery(TEST_NETID, hostname, ns_c_in, ns_t_a, cacheFlag);
+                    dnsMode == "STRICT" ? expectAnswersNotValid(fd, -ETIMEDOUT)
+                                        : expectAnswersValid(fd, AF_INET, "1.2.3.4");
+                });
+            }
+            for (std::thread& thread : threads) {
+                thread.join();
+            }
+
+            const int timeTakenMs = s.timeTakenUs() / 1000;
+            // A loose upper bound is set by adding 1000ms buffer time. Theoretically, it should
+            // just take a bit more than expetTimeout milliseconds for the result.
+            EXPECT_GE(timeTakenMs, config.expectedTimeout);
+            EXPECT_LE(timeTakenMs, config.expectedTimeout + 1000);
+
+            // Recover the server from being unresponsive and try again.
+            tls.setHangOnHandshakeForTesting(false);
+            int fd = resNetworkQuery(TEST_NETID, hostname, ns_c_in, ns_t_a, cacheFlag);
+            expectAnswersValid(fd, AF_INET, "1.2.3.4");
+        }
+    }
 }
 
 TEST_F(ResolverTest, FlushNetworkCache) {
@@ -4574,6 +4756,7 @@
     StartDns(dns2, {});
     test::DnsTlsFrontend workableTls(addr1, "853", addr1, "53");
     test::DnsTlsFrontend unresponsiveTls(addr2, "853", addr2, "53");
+    int validationAttemptsToUnresponsiveTls = 1;
     unresponsiveTls.setHangOnHandshakeForTesting(true);
     ASSERT_TRUE(workableTls.startServer());
     ASSERT_TRUE(unresponsiveTls.startServer());
@@ -4587,7 +4770,9 @@
     // Check the validation results.
     EXPECT_TRUE(WaitForPrivateDnsValidation(workableTls.listen_address(), true));
     EXPECT_TRUE(WaitForPrivateDnsValidation(unusable_addr, false));
-    EXPECT_EQ(unresponsiveTls.acceptConnectionsCount(), 1);  // The validation is still in progress.
+
+    // The validation is still in progress.
+    EXPECT_EQ(unresponsiveTls.acceptConnectionsCount(), validationAttemptsToUnresponsiveTls);
 
     static const struct TestConfig {
         std::vector<std::string> tlsServers;
@@ -4617,13 +4802,23 @@
             SCOPED_TRACE(serverAddr);
             if (serverAddr == workableTls.listen_address()) {
                 if (dnsModeChanged) {
-                    // In despite of the identical IP address, the server is regarded as a different
+                    // Despite the identical IP address, the server is regarded as a different
                     // server when DnsTlsServer.name is different. The resolver treats it as a
                     // different object and begins the validation process.
                     EXPECT_TRUE(WaitForPrivateDnsValidation(serverAddr, true));
                 }
             } else if (serverAddr == unresponsiveTls.listen_address()) {
-                // No revalidation needed for the server which have been marked as in_progesss.
+                if (dnsModeChanged) {
+                    // Despite the identical IP address, the server is regarded as a different
+                    // server when DnsTlsServer.name is different. The resolver treats it as a
+                    // different object and begins the validation process.
+                    validationAttemptsToUnresponsiveTls++;
+
+                    // This is the limitation from DnsTlsFrontend. DnsTlsFrontend can't operate
+                    // concurrently. As soon as there's another connection request,
+                    // DnsTlsFrontend resets the unique_fd to the new connection.
+                    EXPECT_TRUE(WaitForPrivateDnsValidation(serverAddr, false));
+                }
             } else {
                 // Must be unusable_addr.
                 // In opportunistic mode, when a validation for a private DNS server fails, the
@@ -4650,7 +4845,7 @@
             EXPECT_TRUE(WaitForPrivateDnsValidation(unusable_addr, false));
         }
 
-        EXPECT_EQ(unresponsiveTls.acceptConnectionsCount(), 1);
+        EXPECT_EQ(unresponsiveTls.acceptConnectionsCount(), validationAttemptsToUnresponsiveTls);
 
         TlsNameLastTime = config.tlsName;
     }
@@ -4696,17 +4891,18 @@
             {{addr2}, "", false, false},
             {{addr1}, "", false, true},
             {{addr2}, "", false, true},
-            {{addr1}, kDefaultPrivateDnsHostName, false, true},
-            {{addr2}, kDefaultPrivateDnsHostName, false, true},
+
+            // expectNothingHappenWhenServerUnresponsive is false in the two cases because of the
+            // limitation from DnsTlsFrontend which can't operate concurrently.
+            {{addr1}, kDefaultPrivateDnsHostName, false, false},
+            {{addr2}, kDefaultPrivateDnsHostName, false, false},
             {{addr1}, kDefaultPrivateDnsHostName, true, true},
             {{addr2}, kDefaultPrivateDnsHostName, true, true},
 
-            // There's no new validation to start because there are already two validation threads
-            // running (one is for addr1, the other is for addr2). This is because the comparator
-            // doesn't compare DnsTlsServer.name. Keep the design as-is until it's known to be
-            // harmful.
-            {{addr1}, "", true, true},
-            {{addr2}, "", true, true},
+            // expectNothingHappenWhenServerUnresponsive is true in the two cases because of the
+            // limitation from DnsTlsFrontend which can't operate concurrently.
+            {{addr1}, "", true, false},
+            {{addr2}, "", true, false},
             {{addr1}, "", true, true},
             {{addr2}, "", true, true},
     };
@@ -4755,6 +4951,8 @@
                     // It's possible that the resolver hasn't yet started to
                     // connect. Wait a while.
                     std::this_thread::sleep_for(100ms);
+                } else {
+                    EXPECT_TRUE(WaitForPrivateDnsValidation(config.tlsServer, false));
                 }
                 const auto condition = [&]() {
                     return tls.acceptConnectionsCount() == connectCountsBefore + expectCountDiff;
@@ -5096,7 +5294,7 @@
     neverRespondDns.setResponseProbability(0.0);
     StartDns(neverRespondDns, records);
     ScopedSystemProperties scopedSystemProperties(
-            "persist.device_config.netd_native.parallel_lookup", "1");
+            "persist.device_config.netd_native.parallel_lookup_release", "1");
     // The default value of parallel_lookup_sleep_time should be very small
     // that we can ignore in this test case.
     // Re-setup test network to make experiment flag take effect.
@@ -5131,7 +5329,7 @@
     test::DNSResponder dns(listen_addr);
     StartDns(dns, records);
     ScopedSystemProperties scopedSystemProperties1(
-            "persist.device_config.netd_native.parallel_lookup", "1");
+            "persist.device_config.netd_native.parallel_lookup_release", "1");
     constexpr int PARALLEL_LOOKUP_SLEEP_TIME_MS = 500;
     ScopedSystemProperties scopedSystemProperties2(
             "persist.device_config.netd_native.parallel_lookup_sleep_time",
@@ -5165,10 +5363,6 @@
 }
 
 TEST_F(ResolverTest, BlockDnsQueryUidDoesNotLeadToBadServer) {
-    // This test relies on blocking traffic on loopback, which xt_qtaguid does not do.
-    // See aosp/358413 and b/34444781 for why.
-    SKIP_IF_BPF_NOT_SUPPORTED;
-
     constexpr char listen_addr1[] = "127.0.0.4";
     constexpr char listen_addr2[] = "::1";
     test::DNSResponder dns1(listen_addr1);
@@ -5206,6 +5400,138 @@
     EXPECT_EQ(dns2.queries().size(), 0U);
 }
 
+TEST_F(ResolverTest, DnsServerSelection) {
+    test::DNSResponder dns1("127.0.0.3");
+    test::DNSResponder dns2("127.0.0.4");
+    test::DNSResponder dns3("127.0.0.5");
+
+    dns1.setResponseDelayMs(10);
+    dns2.setResponseDelayMs(25);
+    dns3.setResponseDelayMs(50);
+    StartDns(dns1, {{kHelloExampleCom, ns_type::ns_t_a, kHelloExampleComAddrV4}});
+    StartDns(dns2, {{kHelloExampleCom, ns_type::ns_t_a, kHelloExampleComAddrV4}});
+    StartDns(dns3, {{kHelloExampleCom, ns_type::ns_t_a, kHelloExampleComAddrV4}});
+
+    ScopedSystemProperties scopedSystemProperties(kSortNameserversFlag, "1");
+
+    // NOTE: the servers must be sorted alphabetically.
+    std::vector<std::string> serverList = {
+            dns1.listen_address(),
+            dns2.listen_address(),
+            dns3.listen_address(),
+    };
+
+    do {
+        SCOPED_TRACE(fmt::format("testConfig: [{}]", fmt::join(serverList, ", ")));
+        const int queryNum = 50;
+        int64_t accumulatedTime = 0;
+
+        // Restart the testing network to 1) make the flag take effect and 2) reset the statistics.
+        resetNetwork();
+
+        // DnsServerSelection doesn't apply to private DNS.
+        ResolverParamsParcel setupParams = DnsResponderClient::GetDefaultResolverParamsParcel();
+        setupParams.servers = serverList;
+        setupParams.tlsServers.clear();
+        ASSERT_TRUE(mDnsClient.SetResolversFromParcel(setupParams));
+
+        // DNSResponder doesn't handle queries concurrently, so don't allow more than
+        // one in-flight query.
+        for (int i = 0; i < queryNum; i++) {
+            Stopwatch s;
+            int fd = resNetworkQuery(TEST_NETID, kHelloExampleCom, ns_c_in, ns_t_a,
+                                     ANDROID_RESOLV_NO_CACHE_LOOKUP);
+            expectAnswersValid(fd, AF_INET, kHelloExampleComAddrV4);
+            accumulatedTime += s.timeTakenUs();
+        }
+
+        const int dns1Count = dns1.queries().size();
+        const int dns2Count = dns2.queries().size();
+        const int dns3Count = dns3.queries().size();
+
+        // All of the servers have ever been selected. In addition, the less latency server
+        // is selected more frequently.
+        EXPECT_GT(dns1Count, 0);
+        EXPECT_GT(dns2Count, 0);
+        EXPECT_GT(dns3Count, 0);
+        EXPECT_GT(dns1Count, dns2Count);
+        EXPECT_GT(dns2Count, dns3Count);
+
+        const int averageTime = accumulatedTime / queryNum;
+        LOG(INFO) << "ResolverTest#DnsServerSelection: averageTime " << averageTime << "us";
+
+        // Since the average Time might differ depending on parameters, set [10ms, 20ms] as
+        // acceptable range.
+        EXPECT_GE(averageTime, 10000);
+        EXPECT_LE(averageTime, 20000);
+
+        dns1.clearQueries();
+        dns2.clearQueries();
+        dns3.clearQueries();
+    } while (std::next_permutation(serverList.begin(), serverList.end()));
+}
+
+TEST_F(ResolverTest, MultipleDotQueriesInOnePacket) {
+    constexpr char hostname1[] = "query1.example.com.";
+    constexpr char hostname2[] = "query2.example.com.";
+    const std::vector<DnsRecord> records = {
+            {hostname1, ns_type::ns_t_a, "1.2.3.4"},
+            {hostname2, ns_type::ns_t_a, "1.2.3.5"},
+    };
+
+    const std::string addr = getUniqueIPv4Address();
+    test::DNSResponder dns(addr);
+    StartDns(dns, records);
+    test::DnsTlsFrontend tls(addr, "853", addr, "53");
+    ASSERT_TRUE(tls.startServer());
+
+    // Set up resolver to strict mode.
+    auto parcel = DnsResponderClient::GetDefaultResolverParamsParcel();
+    parcel.servers = {addr};
+    parcel.tlsServers = {addr};
+    parcel.tlsName = kDefaultPrivateDnsHostName;
+    parcel.caCertificate = kCaCert;
+    ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel));
+    EXPECT_TRUE(WaitForPrivateDnsValidation(tls.listen_address(), true));
+    EXPECT_TRUE(tls.waitForQueries(1));
+    tls.clearQueries();
+    dns.clearQueries();
+
+    const auto queryAndCheck = [&](const std::string& hostname,
+                                   const std::vector<DnsRecord>& records) {
+        SCOPED_TRACE(hostname);
+
+        const addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_DGRAM};
+        auto [result, timeTakenMs] = safe_getaddrinfo_time_taken(hostname.c_str(), nullptr, hints);
+
+        std::vector<std::string> expectedAnswers;
+        for (const auto& r : records) {
+            if (r.host_name == hostname) expectedAnswers.push_back(r.addr);
+        }
+
+        EXPECT_LE(timeTakenMs, 200);
+        ASSERT_NE(result, nullptr);
+        EXPECT_THAT(ToStrings(result), testing::UnorderedElementsAreArray(expectedAnswers));
+    };
+
+    // Set tls to reply DNS responses in one TCP packet and not to close the connection from its
+    // side.
+    tls.setDelayQueries(2);
+    tls.setDelayQueriesTimeout(500);
+    tls.setPassiveClose(true);
+
+    // Start sending DNS requests at the same time.
+    std::array<std::thread, 2> threads;
+    threads[0] = std::thread(queryAndCheck, hostname1, records);
+    threads[1] = std::thread(queryAndCheck, hostname2, records);
+
+    threads[0].join();
+    threads[1].join();
+
+    // Also check no additional queries due to DoT reconnection.
+    EXPECT_EQ(tls.queries(), 2);
+}
+
 // ResolverMultinetworkTest is used to verify multinetwork functionality. Here's how it works:
 // The resolver sends queries to address A, and then there will be a TunForwarder helping forward
 // the packets to address B, which is the address on which the testing server is listening. The
@@ -5666,7 +5992,6 @@
 
 TEST_F(ResolverMultinetworkTest, DnsWithVpn) {
     SKIP_IF_REMOTE_VERSION_LESS_THAN(mDnsClient.resolvService(), 4);
-    SKIP_IF_BPF_NOT_SUPPORTED;
     constexpr char host_name[] = "ohayou.example.com.";
     constexpr char ipv4_addr[] = "192.0.2.0";
     constexpr char ipv6_addr[] = "2001:db8:cafe:d00d::31";
diff --git a/util.cpp b/util.cpp
index b8df74c..458f3c6 100644
--- a/util.cpp
+++ b/util.cpp
@@ -17,6 +17,7 @@
 
 #include "util.h"
 
+#include <android-base/format.h>
 #include <android-base/parseint.h>
 #include <server_configurable_flags/get_flags.h>
 
@@ -45,3 +46,13 @@
     ParseInt(GetServerConfigurableFlag("netd_native", flagName, ""), &val);
     return val;
 }
+
+std::string timestampToString(const std::chrono::system_clock::time_point& ts) {
+    using std::chrono::duration_cast;
+    using std::chrono::milliseconds;
+    const auto time_sec = std::chrono::system_clock::to_time_t(ts);
+    char buf[32];
+    std::strftime(buf, sizeof(buf), "%H:%M:%S", std::localtime(&time_sec));
+    int ms = duration_cast<milliseconds>(ts.time_since_epoch()).count() % 1000;
+    return fmt::format("{}.{:03d}", buf, ms);
+}
diff --git a/util.h b/util.h
index 38f09b8..bb4a598 100644
--- a/util.h
+++ b/util.h
@@ -17,6 +17,7 @@
 
 #pragma once
 
+#include <chrono>
 #include <string>
 
 #include <netinet/in.h>
@@ -30,6 +31,9 @@
 // TODO: Migrate it to DnsResolverExperiments.cpp
 int getExperimentFlagInt(const std::string& flagName, int defaultValue);
 
+// Convert time_point to readable string format "hr:min:sec.ms".
+std::string timestampToString(const std::chrono::system_clock::time_point& ts);
+
 // When sdk X release branch is created, aosp's sdk version would still be X-1,
 // internal would be X. Also there might be some different setting between real devices and
 // CF. Below is the example for the sdk related properties in later R development stage. (internal
@@ -47,3 +51,8 @@
             android::base::GetUintProperty<uint64_t>("ro.product.first_api_level", 0);
     return std::max(buildVersionSdk + !!buildVersionPreviewSdk, firstApiLevel);
 }
+
+// It's the identical strategy as frameworks/base/core/java/android/os/Build.java did.
+inline bool isUserDebugBuild() {
+    return (android::base::GetProperty("ro.build.type", "user") == "userdebug");
+}