Merge "Sort and group network stats collected from eBPF."
diff --git a/libnetdbpf/Android.bp b/libnetdbpf/Android.bp
index 64a6b66..8592e6f 100644
--- a/libnetdbpf/Android.bp
+++ b/libnetdbpf/Android.bp
@@ -43,7 +43,7 @@
 
     shared_libs: [
         "libbase",
-        "libbpf",
+        "libbpf_android",
         "libutils",
         "liblog",
         "libnetdutils",
@@ -76,7 +76,7 @@
     static_libs: ["libgmock"],
     shared_libs: [
         "libbase",
-        "libbpf",
+        "libbpf_android",
         "liblog",
         "libnetdbpf",
         "libnetdutils",
diff --git a/resolv/Android.bp b/resolv/Android.bp
index 252264d..5e88ab8 100644
--- a/resolv/Android.bp
+++ b/resolv/Android.bp
@@ -22,16 +22,17 @@
         "DnsTlsServer.cpp",
         "DnsTlsSessionCache.cpp",
         "DnsTlsSocket.cpp",
+        "PrivateDnsConfiguration.cpp",
     ],
     // Link everything statically (except for libc) to minimize our dependence
     // on system ABIs
     stl: "libc++_static",
     static_libs: [
         "libbase",
+        "libcrypto",
         "liblog",
         "libnetdutils",
         "libssl",
-        "libcrypto",
     ],
     export_include_dirs: ["include"],
     // TODO: pie in the sky: make this code clang-tidy clean
@@ -48,17 +49,28 @@
 cc_test {
     name: "libnetd_resolv_test",
     defaults: ["netd_defaults"],
+    // Add DnsTls* files since they are not visible outside libnetd_resolv library.
     srcs: [
+        "DnsTlsDispatcher.cpp",
+        "DnsTlsQueryMap.cpp",
+        "DnsTlsTransport.cpp",
+        "DnsTlsServer.cpp",
+        "DnsTlsSessionCache.cpp",
+        "DnsTlsSocket.cpp",
         "dns_tls_test.cpp",
     ],
+    // TODO: Remove the include path "system/netd/include" after Fwmark used in DnsTlsTransport is
+    // moved out.
     include_dirs: [
+        "system/netd/include",
+        "system/netd/resolv/include",
         "system/netd/server",
     ],
     shared_libs: [
         "libbase",
+        "libcrypto",
         "liblog",
         "libnetdutils",
-        "libnetd_resolv",
         "libssl",
     ],
 }
diff --git a/resolv/PrivateDnsConfiguration.cpp b/resolv/PrivateDnsConfiguration.cpp
new file mode 100644
index 0000000..b854a38
--- /dev/null
+++ b/resolv/PrivateDnsConfiguration.cpp
@@ -0,0 +1,363 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "PrivateDnsConfiguration"
+#define DBG 0
+
+#include <log/log.h>
+#include <netdb.h>
+#include <sys/socket.h>
+
+#include "netd_resolv/DnsTlsTransport.h"
+#include "netd_resolv/PrivateDnsConfiguration.h"
+#include "netdutils/BackoffSequence.h"
+
+int resolv_set_private_dns_for_net(unsigned netid, uint32_t mark, const char** servers,
+                                   const unsigned numServers, const char* tlsName,
+                                   const uint8_t** fingerprints, const unsigned numFingerprint) {
+    std::vector<std::string> tlsServers;
+    for (unsigned i = 0; i < numServers; i++) {
+        tlsServers.push_back(std::string(servers[i]));
+    }
+
+    std::set<std::vector<uint8_t>> tlsFingerprints;
+    for (unsigned i = 0; i < numFingerprint; i++) {
+        // Each fingerprint stored are 32(SHA256_SIZE) bytes long.
+        tlsFingerprints.emplace(std::vector<uint8_t>(fingerprints[i], fingerprints[i] + 32));
+    }
+
+    return android::net::gPrivateDnsConfiguration.set(netid, mark, tlsServers, std::string(tlsName),
+                                                      tlsFingerprints);
+}
+
+void resolv_delete_private_dns_for_net(unsigned netid) {
+    android::net::gPrivateDnsConfiguration.clear(netid);
+}
+
+void resolv_get_private_dns_status_for_net(unsigned netid, ExternalPrivateDnsStatus* status) {
+    android::net::gPrivateDnsConfiguration.getStatus(netid, status);
+}
+
+void resolv_register_private_dns_callback(private_dns_validated_callback callback) {
+    android::net::gPrivateDnsConfiguration.setCallback(callback);
+}
+
+namespace android {
+
+using android::netdutils::BackoffSequence;
+
+namespace net {
+
+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_family = AF_UNSPEC, .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV};
+    addrinfo* res;
+
+    int err = getaddrinfo(server, "853", &hints, &res);
+    if (err != 0) {
+        ALOGW("Failed to parse server address (%s): %s", server, gai_strerror(err));
+        return false;
+    }
+
+    memcpy(parsed, res->ai_addr, res->ai_addrlen);
+    freeaddrinfo(res);
+    return true;
+}
+
+void PrivateDnsConfiguration::setCallback(private_dns_validated_callback callback) {
+    if (mCallback == nullptr) {
+        mCallback = callback;
+    }
+}
+
+int PrivateDnsConfiguration::set(int32_t netId, uint32_t mark,
+                                 const std::vector<std::string>& servers, const std::string& name,
+                                 const std::set<std::vector<uint8_t>>& fingerprints) {
+    if (DBG) {
+        ALOGD("PrivateDnsConfiguration::set(%u, %zu, %s, %zu)", netId, servers.size(), name.c_str(),
+              fingerprints.size());
+    }
+
+    const bool explicitlyConfigured = !name.empty() || !fingerprints.empty();
+
+    // Parse the list of servers that has been passed in
+    std::set<DnsTlsServer> tlsServers;
+    for (size_t i = 0; i < servers.size(); ++i) {
+        sockaddr_storage parsed;
+        if (!parseServer(servers[i].c_str(), &parsed)) {
+            return -EINVAL;
+        }
+        DnsTlsServer server(parsed);
+        server.name = name;
+        server.fingerprints = fingerprints;
+        tlsServers.insert(server);
+    }
+
+    std::lock_guard guard(mPrivateDnsLock);
+    if (explicitlyConfigured) {
+        mPrivateDnsModes[netId] = PrivateDnsMode::STRICT;
+    } else if (!tlsServers.empty()) {
+        mPrivateDnsModes[netId] = PrivateDnsMode::OPPORTUNISTIC;
+    } else {
+        mPrivateDnsModes[netId] = PrivateDnsMode::OFF;
+        mPrivateDnsTransports.erase(netId);
+        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) {
+            ALOGE("Memory error while recording private DNS for netId %d", netId);
+            return -ENOMEM;
+        }
+    }
+    auto& tracker = netPair->second;
+
+    // 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 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);
+        }
+    }
+    return 0;
+}
+
+PrivateDnsStatus PrivateDnsConfiguration::getStatus(unsigned netId) {
+    PrivateDnsStatus status{PrivateDnsMode::OFF, {}};
+
+    // This mutex is on the critical path of every DNS lookup.
+    //
+    // If the overhead of mutex acquisition proves too high, we could reduce
+    // it by maintaining an atomic_int32_t counter of TLS-enabled netids, or
+    // by using an RWLock.
+    std::lock_guard guard(mPrivateDnsLock);
+
+    const auto mode = mPrivateDnsModes.find(netId);
+    if (mode == mPrivateDnsModes.end()) return status;
+    status.mode = mode->second;
+
+    const auto netPair = mPrivateDnsTransports.find(netId);
+    if (netPair != mPrivateDnsTransports.end()) {
+        for (const auto& serverPair : netPair->second) {
+            if (serverPair.second == Validation::success) {
+                status.validatedServers.push_back(serverPair.first);
+            }
+        }
+    }
+
+    return status;
+}
+
+void PrivateDnsConfiguration::getStatus(unsigned netId, ExternalPrivateDnsStatus* status) {
+    // This mutex is on the critical path of every DNS lookup.
+    //
+    // If the overhead of mutex acquisition proves too high, we could reduce
+    // it by maintaining an atomic_int32_t counter of TLS-enabled netids, or
+    // by using an RWLock.
+    std::lock_guard guard(mPrivateDnsLock);
+
+    const auto mode = mPrivateDnsModes.find(netId);
+    if (mode == mPrivateDnsModes.end()) return;
+    status->mode = mode->second;
+
+    const auto netPair = mPrivateDnsTransports.find(netId);
+    if (netPair != mPrivateDnsTransports.end()) {
+        status->numServers = netPair->second.size();
+        int count = 0;
+        for (const auto& serverPair : netPair->second) {
+            status->serverStatus[count].ss = serverPair.first.ss;
+            status->serverStatus[count].hostname =
+                    serverPair.first.name.empty() ? "" : serverPair.first.name.c_str();
+            status->serverStatus[count].validation = serverPair.second;
+            /*
+            unsigned numFingerprint = 0;
+            for (const auto& fp : serverPair.first.fingerprints) {
+                std::copy(
+                        fp.begin(), fp.end(),
+                        status->serverStatus[count].fingerprints.fingerprint[numFingerprint].data);
+                numFingerprint++;
+            }
+            status->serverStatus[count].fingerprints.num = numFingerprint;
+            */
+            count++;
+        }
+    }
+}
+
+void PrivateDnsConfiguration::clear(unsigned netId) {
+    if (DBG) {
+        ALOGD("PrivateDnsConfiguration::clear(%u)", netId);
+    }
+    std::lock_guard guard(mPrivateDnsLock);
+    mPrivateDnsModes.erase(netId);
+    mPrivateDnsTransports.erase(netId);
+}
+
+void PrivateDnsConfiguration::validatePrivateDnsProvider(const DnsTlsServer& server,
+                                                         PrivateDnsTracker& tracker, unsigned netId,
+                                                         uint32_t mark) REQUIRES(mPrivateDnsLock) {
+    if (DBG) {
+        ALOGD("validatePrivateDnsProvider(%s, %u)", addrToString(&(server.ss)).c_str(), netId);
+    }
+
+    tracker[server] = Validation::in_process;
+    if (DBG) {
+        ALOGD("Server %s marked as in_process.  Tracker now has size %zu",
+              addrToString(&(server.ss)).c_str(), tracker.size());
+    }
+    // Note that capturing |server| and |netId| in this lambda create copies.
+    std::thread validate_thread([this, server, netId, mark] {
+        // cat /proc/sys/net/ipv4/tcp_syn_retries yields "6".
+        //
+        // Start with a 1 minute delay and backoff to once per hour.
+        //
+        // Assumptions:
+        //     [1] Each TLS validation is ~10KB of certs+handshake+payload.
+        //     [2] Network typically provision clients with <=4 nameservers.
+        //     [3] Average month has 30 days.
+        //
+        // Each validation pass in a given hour is ~1.2MB of data. And 24
+        // such validation passes per day is about ~30MB per month, in the
+        // worst case. Otherwise, this will cost ~600 SYNs per month
+        // (6 SYNs per ip, 4 ips per validation pass, 24 passes per day).
+        auto backoff = BackoffSequence<>::Builder()
+                               .withInitialRetransmissionTime(std::chrono::seconds(60))
+                               .withMaximumRetransmissionTime(std::chrono::seconds(3600))
+                               .build();
+
+        while (true) {
+            // ::validate() is a blocking call that performs network operations.
+            // It can take milliseconds to minutes, up to the SYN retry limit.
+            const bool success = DnsTlsTransport::validate(server, netId, mark);
+            if (DBG) {
+                ALOGD("validateDnsTlsServer returned %d for %s", success,
+                      addrToString(&(server.ss)).c_str());
+            }
+
+            const bool needs_reeval = this->recordPrivateDnsValidation(server, netId, success);
+            if (!needs_reeval) {
+                break;
+            }
+
+            if (backoff.hasNextTimeout()) {
+                std::this_thread::sleep_for(backoff.getNextTimeout());
+            } else {
+                break;
+            }
+        }
+    });
+    validate_thread.detach();
+}
+
+bool PrivateDnsConfiguration::recordPrivateDnsValidation(const DnsTlsServer& server, unsigned netId,
+                                                         bool success) {
+    constexpr bool NEEDS_REEVALUATION = true;
+    constexpr bool DONT_REEVALUATE = false;
+
+    std::lock_guard guard(mPrivateDnsLock);
+
+    auto netPair = mPrivateDnsTransports.find(netId);
+    if (netPair == mPrivateDnsTransports.end()) {
+        ALOGW("netId %u was erased during private DNS validation", netId);
+        return DONT_REEVALUATE;
+    }
+
+    const auto mode = mPrivateDnsModes.find(netId);
+    if (mode == mPrivateDnsModes.end()) {
+        ALOGW("netId %u has no private DNS validation mode", netId);
+        return DONT_REEVALUATE;
+    }
+    const bool modeDoesReevaluation = (mode->second == PrivateDnsMode::STRICT);
+
+    bool reevaluationStatus =
+            (success || !modeDoesReevaluation) ? DONT_REEVALUATE : NEEDS_REEVALUATION;
+
+    auto& tracker = netPair->second;
+    auto serverPair = tracker.find(server);
+    if (serverPair == tracker.end()) {
+        ALOGW("Server %s was removed during private DNS validation",
+              addrToString(&(server.ss)).c_str());
+        success = false;
+        reevaluationStatus = DONT_REEVALUATE;
+    } else if (!(serverPair->first == server)) {
+        // TODO: It doesn't seem correct to overwrite the tracker entry for
+        // |server| down below in this circumstance... Fix this.
+        ALOGW("Server %s was changed during private DNS validation",
+              addrToString(&(server.ss)).c_str());
+        success = false;
+        reevaluationStatus = DONT_REEVALUATE;
+    }
+
+    // Invoke the callback to send a validation event to NetdEventListenerService.
+    if (mCallback != nullptr) {
+        const char* ipLiteral = addrToString(&(server.ss)).c_str();
+        const char* hostname = server.name.empty() ? "" : server.name.c_str();
+        mCallback(netId, ipLiteral, hostname, success);
+    }
+
+    if (success) {
+        tracker[server] = Validation::success;
+        if (DBG) {
+            ALOGD("Validation succeeded for %s! Tracker now has %zu entries.",
+                  addrToString(&(server.ss)).c_str(), tracker.size());
+        }
+    } 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;
+        if (DBG) {
+            ALOGD("Validation failed for %s!", addrToString(&(server.ss)).c_str());
+        }
+    }
+
+    return reevaluationStatus;
+}
+
+// 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);
+}
+
+PrivateDnsConfiguration gPrivateDnsConfiguration;
+
+}  // namespace net
+}  // namespace android
diff --git a/resolv/getaddrinfo.cpp b/resolv/getaddrinfo.cpp
index 0364be4..c229588 100644
--- a/resolv/getaddrinfo.cpp
+++ b/resolv/getaddrinfo.cpp
@@ -127,24 +127,23 @@
     int e_af;
     int e_socktype;
     int e_protocol;
-    const char* e_protostr;
     int e_wild;
 #define WILD_AF(ex) ((ex)->e_wild & 0x01)
 #define WILD_SOCKTYPE(ex) ((ex)->e_wild & 0x02)
 #define WILD_PROTOCOL(ex) ((ex)->e_wild & 0x04)
 };
 
-static const struct explore explore[] = {
-        {PF_INET6, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07},
-        {PF_INET6, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07},
-        {PF_INET6, SOCK_RAW, ANY, NULL, 0x05},
-        {PF_INET, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07},
-        {PF_INET, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07},
-        {PF_INET, SOCK_RAW, ANY, NULL, 0x05},
-        {PF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07},
-        {PF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07},
-        {PF_UNSPEC, SOCK_RAW, ANY, NULL, 0x05},
-        {-1, 0, 0, NULL, 0},
+static const struct explore explore_options[] = {
+        {PF_INET6, SOCK_DGRAM, IPPROTO_UDP, 0x07},
+        {PF_INET6, SOCK_STREAM, IPPROTO_TCP, 0x07},
+        {PF_INET6, SOCK_RAW, ANY, 0x05},
+        {PF_INET, SOCK_DGRAM, IPPROTO_UDP, 0x07},
+        {PF_INET, SOCK_STREAM, IPPROTO_TCP, 0x07},
+        {PF_INET, SOCK_RAW, ANY, 0x05},
+        {PF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, 0x07},
+        {PF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, 0x07},
+        {PF_UNSPEC, SOCK_RAW, ANY, 0x05},
+        {-1, 0, 0, 0},
 };
 
 #define PTON_MAX 16
@@ -230,20 +229,6 @@
         if (error != 0) goto free;                     \
     } while (0)
 
-#define GET_CANONNAME(ai, str)                              \
-    do {                                                    \
-        /* external reference: pai, error and label free */ \
-        error = get_canonname(pai, (ai), (str));            \
-        if (error != 0) goto free;                          \
-    } while (0)
-
-#define ERR(err)                                       \
-    do {                                               \
-        /* external reference: error, and label bad */ \
-        error = (err);                                 \
-        goto bad;                                      \
-    } while (0)
-
 #define MATCH_FAMILY(x, y, w) \
     ((x) == (y) || ((w) && ((x) == PF_UNSPEC || (y) == PF_UNSPEC)))
 #define MATCH(x, y, w) ((x) == (y) || ((w) && ((x) == ANY || (y) == ANY)))
@@ -334,7 +319,7 @@
                                      const struct addrinfo* hints,
                                      const struct android_net_context* netcontext,
                                      struct addrinfo** res) {
-    struct addrinfo sentinel;
+    struct addrinfo sentinel = {};
     struct addrinfo* cur;
     int error = 0;
     struct addrinfo ai;
@@ -347,7 +332,6 @@
     /* hints is allowed to be NULL */
     assert(res != NULL);
     assert(netcontext != NULL);
-    memset(&sentinel, 0, sizeof(sentinel));
     cur = &sentinel;
     pai = &ai;
     pai->ai_flags = 0;
@@ -358,137 +342,149 @@
     pai->ai_canonname = NULL;
     pai->ai_addr = NULL;
     pai->ai_next = NULL;
-
-    if (hostname == NULL && servname == NULL) return EAI_NONAME;
-    if (hints) {
-        /* error check for hints */
-        if (hints->ai_addrlen || hints->ai_canonname || hints->ai_addr || hints->ai_next)
-            ERR(EAI_BADHINTS); /* xxx */
-        if (hints->ai_flags & ~AI_MASK) ERR(EAI_BADFLAGS);
-        switch (hints->ai_family) {
-            case PF_UNSPEC:
-            case PF_INET:
-            case PF_INET6:
-                break;
-            default:
-                ERR(EAI_FAMILY);
+    do {
+        if (hostname == NULL && servname == NULL) {
+            error = EAI_NONAME;
+            break;
         }
-        memcpy(pai, hints, sizeof(*pai));
+        if (hints) {
+            /* error check for hints */
+            if (hints->ai_addrlen || hints->ai_canonname || hints->ai_addr || hints->ai_next) {
+                error = EAI_BADHINTS;
+                break;
+            }
+            if (hints->ai_flags & ~AI_MASK) {
+                error = EAI_BADFLAGS;
+                break;
+            }
 
-        /*
-         * if both socktype/protocol are specified, check if they
-         * are meaningful combination.
-         */
-        if (pai->ai_socktype != ANY && pai->ai_protocol != ANY) {
-            for (ex = explore; ex->e_af >= 0; ex++) {
-                if (pai->ai_family != ex->e_af) continue;
-                if (ex->e_socktype == ANY) continue;
-                if (ex->e_protocol == ANY) continue;
-                if (pai->ai_socktype == ex->e_socktype && pai->ai_protocol != ex->e_protocol) {
-                    ERR(EAI_BADHINTS);
+            if (!(hints->ai_family == PF_UNSPEC || hints->ai_family == PF_INET ||
+                  hints->ai_family == PF_INET6)) {
+                error = EAI_FAMILY;
+                break;
+            }
+            *pai = *hints;
+
+            /*
+             * if both socktype/protocol are specified, check if they
+             * are meaningful combination.
+             */
+            if (pai->ai_socktype != ANY && pai->ai_protocol != ANY) {
+                for (ex = explore_options; ex->e_af >= 0; ex++) {
+                    if (pai->ai_family != ex->e_af) continue;
+                    if (ex->e_socktype == ANY) continue;
+                    if (ex->e_protocol == ANY) continue;
+                    if (pai->ai_socktype == ex->e_socktype && pai->ai_protocol != ex->e_protocol) {
+                        error = EAI_BADHINTS;
+                        break;
+                    }
                 }
+                if (error) break;
             }
         }
-    }
 
-    /*
-     * check for special cases.  (1) numeric servname is disallowed if
-     * socktype/protocol are left unspecified. (2) servname is disallowed
-     * for raw and other inet{,6} sockets.
-     */
-    if (MATCH_FAMILY(pai->ai_family, PF_INET, 1)
-        || MATCH_FAMILY(pai->ai_family, PF_INET6, 1)
-    ) {
-        ai0 = *pai; /* backup *pai */
+        /*
+         * check for special cases.  (1) numeric servname is disallowed if
+         * socktype/protocol are left unspecified. (2) servname is disallowed
+         * for raw and other inet{,6} sockets.
+         */
+        if (MATCH_FAMILY(pai->ai_family, PF_INET, 1)
+            || MATCH_FAMILY(pai->ai_family, PF_INET6, 1)
+        ) {
+            ai0 = *pai; /* backup *pai */
 
-        if (pai->ai_family == PF_UNSPEC) {
-            pai->ai_family = PF_INET6;
-        }
-        error = get_portmatch(pai, servname);
-        if (error) ERR(error);
+            if (pai->ai_family == PF_UNSPEC) {
+                pai->ai_family = PF_INET6;
+            }
+            error = get_portmatch(pai, servname);
+            if (error) break;
 
-        *pai = ai0;
-    }
-
-    ai0 = *pai;
-
-    /* NULL hostname, or numeric hostname */
-    for (ex = explore; ex->e_af >= 0; ex++) {
-        *pai = ai0;
-
-        /* PF_UNSPEC entries are prepared for DNS queries only */
-        if (ex->e_af == PF_UNSPEC) continue;
-
-        if (!MATCH_FAMILY(pai->ai_family, ex->e_af, WILD_AF(ex))) continue;
-        if (!MATCH(pai->ai_socktype, ex->e_socktype, WILD_SOCKTYPE(ex))) continue;
-        if (!MATCH(pai->ai_protocol, ex->e_protocol, WILD_PROTOCOL(ex))) continue;
-
-        if (pai->ai_family == PF_UNSPEC) pai->ai_family = ex->e_af;
-        if (pai->ai_socktype == ANY && ex->e_socktype != ANY) pai->ai_socktype = ex->e_socktype;
-        if (pai->ai_protocol == ANY && ex->e_protocol != ANY) pai->ai_protocol = ex->e_protocol;
-
-        if (hostname == NULL)
-            error = explore_null(pai, servname, &cur->ai_next);
-        else
-            error = explore_numeric_scope(pai, hostname, servname, &cur->ai_next);
-
-        if (error) goto free;
-
-        while (cur->ai_next) cur = cur->ai_next;
-    }
-
-    /*
-     * XXX
-     * If numeric representation of AF1 can be interpreted as FQDN
-     * representation of AF2, we need to think again about the code below.
-     */
-    if (sentinel.ai_next) goto good;
-
-    if (hostname == NULL) ERR(EAI_NODATA);
-    if (pai->ai_flags & AI_NUMERICHOST) ERR(EAI_NONAME);
-
-    /*
-     * hostname as alphabetical name.
-     * we would like to prefer AF_INET6 than AF_INET, so we'll make a
-     * outer loop by AFs.
-     */
-    for (ex = explore; ex->e_af >= 0; ex++) {
-        *pai = ai0;
-
-        /* require exact match for family field */
-        if (pai->ai_family != ex->e_af) continue;
-
-        if (!MATCH(pai->ai_socktype, ex->e_socktype, WILD_SOCKTYPE(ex))) {
-            continue;
-        }
-        if (!MATCH(pai->ai_protocol, ex->e_protocol, WILD_PROTOCOL(ex))) {
-            continue;
+            *pai = ai0;
         }
 
-        if (pai->ai_socktype == ANY && ex->e_socktype != ANY) pai->ai_socktype = ex->e_socktype;
-        if (pai->ai_protocol == ANY && ex->e_protocol != ANY) pai->ai_protocol = ex->e_protocol;
+        ai0 = *pai;
 
-        error = explore_fqdn(pai, hostname, servname, &cur->ai_next, netcontext);
+        /* NULL hostname, or numeric hostname */
+        for (ex = explore_options; ex->e_af >= 0; ex++) {
+            *pai = ai0;
 
-        while (cur && cur->ai_next) cur = cur->ai_next;
-    }
+            /* PF_UNSPEC entries are prepared for DNS queries only */
+            if (ex->e_af == PF_UNSPEC) continue;
 
-    /* XXX */
-    if (sentinel.ai_next) error = 0;
+            if (!MATCH_FAMILY(pai->ai_family, ex->e_af, WILD_AF(ex))) continue;
+            if (!MATCH(pai->ai_socktype, ex->e_socktype, WILD_SOCKTYPE(ex))) continue;
+            if (!MATCH(pai->ai_protocol, ex->e_protocol, WILD_PROTOCOL(ex))) continue;
 
-    if (error) goto free;
-    if (error == 0) {
+            if (pai->ai_family == PF_UNSPEC) pai->ai_family = ex->e_af;
+            if (pai->ai_socktype == ANY && ex->e_socktype != ANY) pai->ai_socktype = ex->e_socktype;
+            if (pai->ai_protocol == ANY && ex->e_protocol != ANY) pai->ai_protocol = ex->e_protocol;
+
+            if (hostname == NULL)
+                error = explore_null(pai, servname, &cur->ai_next);
+            else
+                error = explore_numeric_scope(pai, hostname, servname, &cur->ai_next);
+
+            if (error) break;
+
+            while (cur->ai_next) cur = cur->ai_next;
+        }
+        if (error) break;
+
+        /*
+         * XXX
+         * If numeric representation of AF1 can be interpreted as FQDN
+         * representation of AF2, we need to think again about the code below.
+         */
+        if (sentinel.ai_next) break;
+
+        if (hostname == NULL) {
+            error = EAI_NODATA;
+            break;
+        }
+        if (pai->ai_flags & AI_NUMERICHOST) {
+            error = EAI_NONAME;
+            break;
+        }
+
+        /*
+         * hostname as alphabetical name.
+         * we would like to prefer AF_INET6 than AF_INET, so we'll make a
+         * outer loop by AFs.
+         */
+        for (ex = explore_options; ex->e_af >= 0; ex++) {
+            *pai = ai0;
+
+            /* require exact match for family field */
+            if (pai->ai_family != ex->e_af) continue;
+
+            if (!MATCH(pai->ai_socktype, ex->e_socktype, WILD_SOCKTYPE(ex))) {
+                continue;
+            }
+            if (!MATCH(pai->ai_protocol, ex->e_protocol, WILD_PROTOCOL(ex))) {
+                continue;
+            }
+
+            if (pai->ai_socktype == ANY && ex->e_socktype != ANY) pai->ai_socktype = ex->e_socktype;
+            if (pai->ai_protocol == ANY && ex->e_protocol != ANY) pai->ai_protocol = ex->e_protocol;
+
+            error = explore_fqdn(pai, hostname, servname, &cur->ai_next, netcontext);
+
+            while (cur->ai_next) cur = cur->ai_next;
+        }
+
         if (sentinel.ai_next) {
-        good:
-            *res = sentinel.ai_next;
-            return 0;
-        } else
+            error = 0;
+        } else if (error == 0) {
             error = EAI_FAIL;
+        }
+    } while (0);
+
+    if (error) {
+        freeaddrinfo(sentinel.ai_next);
+        *res = NULL;
+    } else {
+        *res = sentinel.ai_next;
     }
-free:
-bad:
-    freeaddrinfo(sentinel.ai_next);
-    *res = NULL;
     return error;
 }
 
@@ -566,15 +562,9 @@
 
     if (pai->ai_flags & AI_PASSIVE) {
         GET_AI(cur->ai_next, afd, afd->a_addrany);
-        /* xxx meaningless?
-         * GET_CANONNAME(cur->ai_next, "anyaddr");
-         */
         GET_PORT(cur->ai_next, servname);
     } else {
         GET_AI(cur->ai_next, afd, afd->a_loopback);
-        /* xxx meaningless?
-         * GET_CANONNAME(cur->ai_next, "localhost");
-         */
         GET_PORT(cur->ai_next, servname);
     }
     cur = cur->ai_next;
@@ -625,18 +615,21 @@
                  * the canonical name, based on a
                  * clarification in rfc2553bis-03.
                  */
-                GET_CANONNAME(cur->ai_next, canonname);
+                error = get_canonname(pai, cur->ai_next, canonname);
+                if (error != 0) {
+                    freeaddrinfo(sentinel.ai_next);
+                    return error;
+                }
             }
             while (cur->ai_next) cur = cur->ai_next;
         } else
-            ERR(EAI_FAMILY); /*xxx*/
+            return EAI_FAMILY;
     }
 
     *res = sentinel.ai_next;
     return 0;
 
 free:
-bad:
     freeaddrinfo(sentinel.ai_next);
     return error;
 }
@@ -886,7 +879,8 @@
 
 static struct addrinfo* getanswer(const querybuf* answer, int anslen, const char* qname, int qtype,
                                   const struct addrinfo* pai) {
-    struct addrinfo sentinel, *cur;
+    struct addrinfo sentinel = {};
+    struct addrinfo *cur;
     struct addrinfo ai;
     const struct afd* afd;
     char* canonname;
@@ -905,7 +899,6 @@
     assert(qname != NULL);
     assert(pai != NULL);
 
-    memset(&sentinel, 0, sizeof(sentinel));
     cur = &sentinel;
 
     canonname = NULL;
@@ -1439,14 +1432,13 @@
 
 static int dns_getaddrinfo(const char* name, const addrinfo* pai,
                            const android_net_context* netcontext, addrinfo** rv) {
-    struct addrinfo* ai;
-    struct addrinfo sentinel, *cur;
+    struct addrinfo *ai, *cur;
+    struct addrinfo sentinel = {};
     struct res_target q, q2;
     res_state res;
 
     memset(&q, 0, sizeof(q));
     memset(&q2, 0, sizeof(q2));
-    memset(&sentinel, 0, sizeof(sentinel));
     cur = &sentinel;
 
     querybuf* buf = (querybuf*) malloc(sizeof(*buf));
@@ -1526,7 +1518,6 @@
      */
     res_setnetcontext(res, netcontext);
     if (res_searchN(name, &q, res) < 0) {
-        res_put_state(res);
         free(buf);
         free(buf2);
         return EAI_NODATA;  // TODO: Decode error from h_errno like we do below
@@ -1543,7 +1534,6 @@
     free(buf);
     free(buf2);
     if (sentinel.ai_next == NULL) {
-        res_put_state(res);
         switch (h_errno) {
             case HOST_NOT_FOUND:
                 return EAI_NODATA;
@@ -1556,8 +1546,6 @@
 
     _rfc6724_sort(&sentinel, netcontext->app_mark, netcontext->uid);
 
-    res_put_state(res);
-
     *rv = sentinel.ai_next;
     return 0;
 }
@@ -1629,11 +1617,10 @@
 }
 
 static bool files_getaddrinfo(const char* name, const addrinfo* pai, addrinfo** res) {
-    struct addrinfo sentinel, *cur;
-    struct addrinfo* p;
+    struct addrinfo sentinel = {};
+    struct addrinfo *p, *cur;
     FILE* hostf = NULL;
 
-    memset(&sentinel, 0, sizeof(sentinel));
     cur = &sentinel;
 
     _sethtent(&hostf);
diff --git a/resolv/gethnamaddr.cpp b/resolv/gethnamaddr.cpp
index 834a962..b36f5c8 100644
--- a/resolv/gethnamaddr.cpp
+++ b/resolv/gethnamaddr.cpp
@@ -137,10 +137,13 @@
 
 static bool _dns_gethtbyaddr(const unsigned char* uaddr, int len, int af,
                              const android_net_context* netcontext, getnamaddr* info);
-static bool _dns_gethtbyname(const char* name, int af, getnamaddr* info);
+static int _dns_gethtbyname(const char* name, int af, getnamaddr* info);
 
-static struct hostent* gethostbyname_internal(const char*, int, res_state, struct hostent*, char*,
-                                              size_t, int*, const struct android_net_context*);
+static int gethostbyname_internal(const char* name, int af, res_state res, hostent* hp, char* hbuf,
+                                  size_t hbuflen, int* errorp,
+                                  const android_net_context* netcontext);
+static int gethostbyname_internal_real(const char* name, int af, res_state res, hostent* hp,
+                                       char* buf, size_t buflen, int* he);
 static struct hostent* android_gethostbyaddrfornetcontext_proxy_internal(
         const void*, socklen_t, int, struct hostent*, char*, size_t, int*,
         const struct android_net_context*);
@@ -466,10 +469,9 @@
     return NULL;
 }
 
-static struct hostent* gethostbyname_internal_real(const char* name, int af, res_state res,
-                                                   struct hostent* hp, char* buf, size_t buflen,
-                                                   int* he) {
-    struct getnamaddr info;
+static int gethostbyname_internal_real(const char* name, int af, res_state res, hostent* hp,
+                                       char* buf, size_t buflen, int* he) {
+    getnamaddr info;
     size_t size;
 
     _DIAGASSERT(name != NULL);
@@ -484,7 +486,7 @@
         default:
             *he = NETDB_INTERNAL;
             errno = EAFNOSUPPORT;
-            return NULL;
+            return EAI_FAMILY;
     }
     if (buflen < size) goto nospc;
 
@@ -530,16 +532,18 @@
     info.buflen = buflen;
     info.he = he;
     if (!_hf_gethtbyname2(name, af, &info)) {
-        if (!_dns_gethtbyname(name, af, &info)) {
-            return NULL;
+        int error = _dns_gethtbyname(name, af, &info);
+        if (error != 0) {
+            return error;
         }
     }
     *he = NETDB_SUCCESS;
-    return hp;
+    return 0;
 nospc:
     *he = NETDB_INTERNAL;
     errno = ENOSPC;
-    return NULL;
+    // Bad arguments
+    return EAI_FAIL;
 fake:
     HENT_ARRAY(hp->h_addr_list, 1, buf, buflen);
     HENT_ARRAY(hp->h_aliases, 0, buf, buflen);
@@ -549,7 +553,7 @@
 
     if (inet_pton(af, name, buf) <= 0) {
         *he = HOST_NOT_FOUND;
-        return NULL;
+        return EAI_NODATA;
     }
     hp->h_addr_list[0] = buf;
     hp->h_addr_list[1] = NULL;
@@ -558,14 +562,13 @@
     HENT_SCOPY(hp->h_name, name, buf, buflen);
     if (res->options & RES_USE_INET6) map_v4v6_hostent(hp, &buf, buf + buflen);
     *he = NETDB_SUCCESS;
-    return hp;
+    return 0;
 }
 
 // very similar in proxy-ness to android_getaddrinfo_proxy
-static struct hostent* gethostbyname_internal(const char* name, int af, res_state res,
-                                              struct hostent* hp, char* hbuf, size_t hbuflen,
-                                              int* errorp,
-                                              const struct android_net_context* netcontext) {
+static int gethostbyname_internal(const char* name, int af, res_state res, hostent* hp, char* hbuf,
+                                  size_t hbuflen, int* errorp,
+                                  const android_net_context* netcontext) {
     res_setnetcontext(res, netcontext);
     return gethostbyname_internal_real(name, af, res, hp, hbuf, hbuflen, errorp);
 }
@@ -691,7 +694,6 @@
                 af = AF_INET;
                 len = NS_INADDRSZ;
             }
-            res_put_state(res);
         }
 
         /* if this is not something we're looking for, skip it. */
@@ -841,7 +843,7 @@
     }
 }
 
-static bool _dns_gethtbyname(const char* name, int addr_type, getnamaddr* info) {
+static int _dns_gethtbyname(const char* name, int addr_type, getnamaddr* info) {
     int n, type;
     struct hostent* hp;
     res_state res;
@@ -858,32 +860,37 @@
             type = T_AAAA;
             break;
         default:
-            return false;
+            return EAI_FAMILY;
     }
     querybuf* buf = (querybuf*) malloc(sizeof(querybuf));
     if (buf == NULL) {
         *info->he = NETDB_INTERNAL;
-        return false;
+        return EAI_MEMORY;
     }
     res = res_get_state();
     if (res == NULL) {
         free(buf);
-        return false;
+        return EAI_MEMORY;
     }
     n = res_nsearch(res, name, C_IN, type, buf->buf, (int) sizeof(buf->buf));
     if (n < 0) {
         free(buf);
         debugprintf("res_nsearch failed (%d)\n", res, n);
-        res_put_state(res);
-        return false;
+        return EAI_NODATA;
     }
     hp = getanswer(buf, n, name, type, res, info->hp, info->buf, info->buflen, info->he);
     free(buf);
-    res_put_state(res);
     if (hp == NULL) {
-        return false;
+        switch (h_errno) {
+            case HOST_NOT_FOUND:
+                return EAI_NODATA;
+            case TRY_AGAIN:
+                return EAI_AGAIN;
+            default:
+                return EAI_FAIL;
+        }
     }
-    return true;
+    return 0;
 }
 
 static bool _dns_gethtbyaddr(const unsigned char* uaddr, int len, int af,
@@ -940,13 +947,11 @@
     if (n < 0) {
         free(buf);
         debugprintf("res_nquery failed (%d)\n", res, n);
-        res_put_state(res);
         return false;
     }
     hp = getanswer(buf, n, qbuf, T_PTR, res, info->hp, info->buf, info->buflen, info->he);
     free(buf);
     if (hp == NULL) {
-        res_put_state(res);
         return false;
     }
 
@@ -970,7 +975,6 @@
         memcpy(bf + NS_INADDRSZ, NAT64_PAD, sizeof(NAT64_PAD));
     }
 
-    res_put_state(res);
     *info->he = NETDB_SUCCESS;
     return true;
 
@@ -984,16 +988,18 @@
  * Non-reentrant versions.
  */
 
-struct hostent* android_gethostbynamefornetcontext(const char* name, int af,
-                                                   const struct android_net_context* netcontext) {
-    struct hostent* hp;
+int android_gethostbynamefornetcontext(const char* name, int af,
+                                       const struct android_net_context* netcontext, hostent** hp) {
+    int error;
     res_state res = res_get_state();
-    if (res == NULL) return NULL;
-    struct res_static* rs = res_get_static();  // For thread-safety.
-    hp = gethostbyname_internal(name, af, res, &rs->host, rs->hostbuf, sizeof(rs->hostbuf),
-                                &h_errno, netcontext);
-    res_put_state(res);
-    return hp;
+    if (res == NULL) return EAI_MEMORY;
+    res_static* rs = res_get_static();  // For thread-safety.
+    error = gethostbyname_internal(name, af, res, &rs->host, rs->hostbuf, sizeof(rs->hostbuf),
+                                   &h_errno, netcontext);
+    if (error == 0) {
+        *hp = &rs->host;
+    }
+    return error;
 }
 
 struct hostent* android_gethostbyaddrfornetcontext(const void* addr, socklen_t len, int af,
diff --git a/resolv/include/netd_resolv/DnsTlsDispatcher.h b/resolv/include/netd_resolv/DnsTlsDispatcher.h
index 0bb19f2..45142b8 100644
--- a/resolv/include/netd_resolv/DnsTlsDispatcher.h
+++ b/resolv/include/netd_resolv/DnsTlsDispatcher.h
@@ -38,8 +38,8 @@
 
 // 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 LIBNETD_RESOLV_TLS_EXPORT DnsTlsDispatcher {
-public:
+class DnsTlsDispatcher {
+  public:
     // Default constructor.
     DnsTlsDispatcher();
 
diff --git a/resolv/include/netd_resolv/DnsTlsQueryMap.h b/resolv/include/netd_resolv/DnsTlsQueryMap.h
index 4c8010c..f013e01 100644
--- a/resolv/include/netd_resolv/DnsTlsQueryMap.h
+++ b/resolv/include/netd_resolv/DnsTlsQueryMap.h
@@ -35,8 +35,8 @@
 
 // Keeps track of queries and responses.  This class matches responses with queries.
 // All methods are thread-safe and non-blocking.
-class LIBNETD_RESOLV_TLS_EXPORT DnsTlsQueryMap {
-public:
+class DnsTlsQueryMap {
+  public:
     struct Query {
         // The new ID number assigned to this query.
         uint16_t newId;
diff --git a/resolv/include/netd_resolv/DnsTlsServer.h b/resolv/include/netd_resolv/DnsTlsServer.h
index 752dc5f..bb161f2 100644
--- a/resolv/include/netd_resolv/DnsTlsServer.h
+++ b/resolv/include/netd_resolv/DnsTlsServer.h
@@ -30,7 +30,7 @@
 
 // DnsTlsServer represents a recursive resolver that supports, or may support, a
 // secure protocol.
-struct LIBNETD_RESOLV_TLS_EXPORT DnsTlsServer {
+struct DnsTlsServer {
     // Default constructor.
     DnsTlsServer() {}
 
@@ -68,7 +68,7 @@
 };
 
 // This comparison only checks the IP address.  It ignores ports, names, and fingerprints.
-struct LIBNETD_RESOLV_TLS_EXPORT AddressComparator {
+struct AddressComparator {
     bool operator() (const DnsTlsServer& x, const DnsTlsServer& y) const;
 };
 
diff --git a/resolv/include/netd_resolv/DnsTlsSocket.h b/resolv/include/netd_resolv/DnsTlsSocket.h
index 7190dd2..195705b 100644
--- a/resolv/include/netd_resolv/DnsTlsSocket.h
+++ b/resolv/include/netd_resolv/DnsTlsSocket.h
@@ -46,8 +46,8 @@
 // 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).
-class LIBNETD_RESOLV_TLS_EXPORT DnsTlsSocket : public IDnsTlsSocket {
-public:
+class DnsTlsSocket : public IDnsTlsSocket {
+  public:
     DnsTlsSocket(const DnsTlsServer& server, unsigned mark,
                  IDnsTlsSocketObserver* _Nonnull observer,
                  DnsTlsSessionCache* _Nonnull cache) :
diff --git a/resolv/include/netd_resolv/DnsTlsTransport.h b/resolv/include/netd_resolv/DnsTlsTransport.h
index 011be61..4590868 100644
--- a/resolv/include/netd_resolv/DnsTlsTransport.h
+++ b/resolv/include/netd_resolv/DnsTlsTransport.h
@@ -41,8 +41,8 @@
 
 // Manages at most one DnsTlsSocket at a time.  This class handles socket lifetime issues,
 // such as reopening the socket and reissuing pending queries.
-class LIBNETD_RESOLV_TLS_EXPORT DnsTlsTransport : public IDnsTlsSocketObserver {
-public:
+class DnsTlsTransport : public IDnsTlsSocketObserver {
+  public:
     DnsTlsTransport(const DnsTlsServer& server, unsigned mark,
                     IDnsTlsSocketFactory* _Nonnull factory) :
             mMark(mark), mServer(server), mFactory(factory) {}
diff --git a/resolv/include/netd_resolv/PrivateDnsConfiguration.h b/resolv/include/netd_resolv/PrivateDnsConfiguration.h
new file mode 100644
index 0000000..cee0205
--- /dev/null
+++ b/resolv/include/netd_resolv/PrivateDnsConfiguration.h
@@ -0,0 +1,78 @@
+/*
+ * 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 NETD_RESOLV_PRIVATEDNSCONFIGURATION_H
+#define NETD_RESOLV_PRIVATEDNSCONFIGURATION_H
+
+#include <list>
+#include <map>
+#include <vector>
+
+#include <android-base/thread_annotations.h>
+
+#include "DnsTlsServer.h"
+#include "resolv.h"
+
+namespace android {
+namespace net {
+
+struct PrivateDnsStatus {
+    PrivateDnsMode mode;
+    std::list<DnsTlsServer> validatedServers;
+};
+
+class PrivateDnsConfiguration {
+  public:
+    int set(int32_t netId, uint32_t mark, const std::vector<std::string>& servers,
+            const std::string& name, const std::set<std::vector<uint8_t>>& fingerprints);
+
+    PrivateDnsStatus getStatus(unsigned netId);
+
+    // Externally used for netd.
+    void getStatus(unsigned netId, ExternalPrivateDnsStatus* status);
+
+    int getPrivateDnsModeAndStatus(unsigned netId);
+    void clear(unsigned netId);
+    void setCallback(private_dns_validated_callback callback);
+
+  private:
+    typedef std::map<DnsTlsServer, Validation, AddressComparator> PrivateDnsTracker;
+
+    void validatePrivateDnsProvider(const DnsTlsServer& server, PrivateDnsTracker& tracker,
+                                    unsigned netId, uint32_t mark) REQUIRES(mPrivateDnsLock);
+
+    bool recordPrivateDnsValidation(const DnsTlsServer& server, unsigned netId, bool success);
+
+    // 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 needsValidation(const PrivateDnsTracker& tracker, const DnsTlsServer& server);
+
+    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.
+    std::map<unsigned, PrivateDnsTracker> mPrivateDnsTransports GUARDED_BY(mPrivateDnsLock);
+    private_dns_validated_callback mCallback = nullptr;
+};
+
+extern PrivateDnsConfiguration gPrivateDnsConfiguration;
+
+}  // namespace net
+}  // namespace android
+
+#endif /* NETD_RESOLV_PRIVATEDNSCONFIGURATION_H */
diff --git a/resolv/include/netd_resolv/params.h b/resolv/include/netd_resolv/params.h
index c3f3363..939bfc4 100644
--- a/resolv/include/netd_resolv/params.h
+++ b/resolv/include/netd_resolv/params.h
@@ -36,17 +36,12 @@
     int base_timeout_msec;      // base query retry timeout (if 0, use RES_TIMEOUT)
 };
 
-typedef enum { res_goahead, res_nextns, res_modified, res_done, res_error } res_sendhookact;
+// The DNS over TLS mode on a specific netId.
+enum class PrivateDnsMode : uint8_t { OFF, OPPORTUNISTIC, STRICT };
 
-typedef res_sendhookact (*res_send_qhook)(struct sockaddr* const*, const uint8_t**, int*, uint8_t*,
-                                          int, int*);
-
-typedef res_sendhookact (*res_send_rhook)(const struct sockaddr*, const uint8_t*, int, uint8_t*,
-                                          int, int*);
+// 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 };
 
 #define LIBNETD_RESOLV_PUBLIC extern "C" [[gnu::visibility("default")]]
 
-// TODO: Remove it after we move PrivateDnsConfiguration and qhook() into libnetd_resolv.
-#define LIBNETD_RESOLV_TLS_EXPORT [[gnu::visibility("default")]]
-
 #endif  // NETD_RESOLV_PARAMS_H
diff --git a/resolv/include/netd_resolv/resolv.h b/resolv/include/netd_resolv/resolv.h
index 96133e5..9176996 100644
--- a/resolv/include/netd_resolv/resolv.h
+++ b/resolv/include/netd_resolv/resolv.h
@@ -75,7 +75,6 @@
     unsigned dns_mark;
     uid_t uid;
     unsigned flags;
-    res_send_qhook qhook;
 };
 
 #define NET_CONTEXT_INVALID_UID ((uid_t) -1)
@@ -83,10 +82,23 @@
 #define NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS 0x00000001
 #define NET_CONTEXT_FLAG_USE_EDNS 0x00000002
 
+struct ExternalPrivateDnsStatus {
+    PrivateDnsMode mode;
+    unsigned numServers;
+    struct PrivateDnsInfo {
+        sockaddr_storage ss;
+        const char* hostname;
+        Validation validation;
+    } serverStatus[MAXNS];
+};
+
+typedef void (*private_dns_validated_callback)(unsigned netid, const char* server,
+                                               const char* hostname, bool success);
+
 LIBNETD_RESOLV_PUBLIC hostent* android_gethostbyaddrfornetcontext(const void*, socklen_t, int,
                                                                   const android_net_context*);
-LIBNETD_RESOLV_PUBLIC hostent* android_gethostbynamefornetcontext(const char*, int,
-                                                                  const android_net_context*);
+LIBNETD_RESOLV_PUBLIC int android_gethostbynamefornetcontext(const char*, int,
+                                                             const android_net_context*, hostent**);
 LIBNETD_RESOLV_PUBLIC int android_getaddrinfofornetcontext(const char*, const char*,
                                                            const addrinfo*,
                                                            const android_net_context*, addrinfo**);
@@ -96,6 +108,22 @@
                                                          unsigned numservers, const char* domains,
                                                          const __res_params* params);
 
+LIBNETD_RESOLV_PUBLIC int resolv_set_private_dns_for_net(unsigned netid, uint32_t mark,
+                                                         const char** servers,
+                                                         const unsigned numServers,
+                                                         const char* tlsName,
+                                                         const uint8_t** fingerprints,
+                                                         const unsigned numFingerprints);
+
+LIBNETD_RESOLV_PUBLIC void resolv_delete_private_dns_for_net(unsigned netid);
+
+LIBNETD_RESOLV_PUBLIC void resolv_get_private_dns_status_for_net(unsigned netid,
+                                                                 ExternalPrivateDnsStatus* status);
+
+// Register callback to listen whether private DNS validated
+LIBNETD_RESOLV_PUBLIC void resolv_register_private_dns_callback(
+        private_dns_validated_callback callback);
+
 // Flush the cache associated with a certain network
 LIBNETD_RESOLV_PUBLIC void resolv_flush_cache_for_net(unsigned netid);
 
diff --git a/resolv/libnetd_resolv.map.txt b/resolv/libnetd_resolv.map.txt
index bb1e408..e139ab7 100644
--- a/resolv/libnetd_resolv.map.txt
+++ b/resolv/libnetd_resolv.map.txt
@@ -28,53 +28,10 @@
     android_net_res_stats_get_usable_servers;
     resolv_delete_cache_for_net;
     resolv_set_nameservers_for_net;
-
-    # These symbol names are generated by 'nm -D libnetd_resolv.so' with reverting the commit
-    # (aosp/801454). They are temporarily necessary for netd to access DnsTls* classes before
-    # we move qhook() and PrivateDnsConfiguration to libnetd_resolv library.
-    # TODO: Remove them after we hide DnsTls* stuff thus netd no longer needs to access them.
-    _ZN7android3net12DnsTlsSocket10initializeEv;
-    _ZN7android3net12DnsTlsSocket10sslConnectEi;
-    _ZN7android3net12DnsTlsSocket10tcpConnectEv;
-    _ZN7android3net12DnsTlsSocket12readResponseEv;
-    _ZN7android3net12DnsTlsSocket13sslDisconnectEv;
-    _ZN7android3net12DnsTlsSocket4loopEv;
-    _ZN7android3net12DnsTlsSocket5queryEtNS_9netdutils5SliceE;
-    _ZN7android3net12DnsTlsSocket7sslReadENS_9netdutils5SliceEb;
-    _ZN7android3net12DnsTlsSocket8sslWriteENS_9netdutils5SliceE;
-    _ZN7android3net12DnsTlsSocket9sendQueryERKNS1_5QueryE;
-    _ZN7android3net12DnsTlsSocketD0Ev;
-    _ZN7android3net12DnsTlsSocketD1Ev;
-    _ZN7android3net12DnsTlsSocketD2Ev;
-    _ZN7android3net14DnsTlsQueryMap10onResponseENSt3__16vectorIhNS2_9allocatorIhEEEE;
-    _ZN7android3net14DnsTlsQueryMap11recordQueryENS_9netdutils5SliceE;
-    _ZN7android3net14DnsTlsQueryMap5clearEv;
-    _ZN7android3net14DnsTlsQueryMap5emptyEv;
-    _ZN7android3net14DnsTlsQueryMap6expireEPNS1_12QueryPromiseE;
-    _ZN7android3net14DnsTlsQueryMap6getAllEv;
-    _ZN7android3net14DnsTlsQueryMap7cleanupEv;
-    _ZN7android3net14DnsTlsQueryMap9getFreeIdEv;
-    _ZN7android3net14DnsTlsQueryMap9markTriedEt;
-    _ZN7android3net15DnsTlsTransport10onResponseENSt3__16vectorIhNS2_9allocatorIhEEEE;
-    _ZN7android3net15DnsTlsTransport11doReconnectEv;
-    _ZN7android3net15DnsTlsTransport5queryENS_9netdutils5SliceE;
-    _ZN7android3net15DnsTlsTransport8onClosedEv;
-    _ZN7android3net15DnsTlsTransport8validateERKNS0_12DnsTlsServerEjj;
-    _ZN7android3net15DnsTlsTransport9doConnectEv;
-    _ZN7android3net15DnsTlsTransport9sendQueryENS0_14DnsTlsQueryMap5QueryE;
-    _ZN7android3net15DnsTlsTransportD0Ev;
-    _ZN7android3net15DnsTlsTransportD1Ev;
-    _ZN7android3net15DnsTlsTransportD2Ev;
-    _ZN7android3net16DnsTlsDispatcher5queryERKNS0_12DnsTlsServerEjNS_9netdutils5SliceES6_Pi;
-    _ZN7android3net16DnsTlsDispatcher5queryERKNSt3__14listINS0_12DnsTlsServerENS2_9allocatorIS4_EEEEjNS_9netdutils5SliceESB_Pi;
-    _ZN7android3net16DnsTlsDispatcherC1Ev;
-    _ZN7android3net16DnsTlsDispatcherC2Ev;
-    _ZNK7android3net12DnsTlsServer23wasExplicitlyConfiguredEv;
-    _ZNK7android3net12DnsTlsServereqERKS1_;
-    _ZNK7android3net12DnsTlsServerltERKS1_;
-    _ZNK7android3net16DnsTlsDispatcher20getOrderedServerListERKNSt3__14listINS0_12DnsTlsServerENS2_9allocatorIS4_EEEEj;
-    _ZNK7android3net17AddressComparatorclERKNS0_12DnsTlsServerES4_;
-    _ZTVN7android3net15DnsTlsTransportE;
+    resolv_set_private_dns_for_net;
+    resolv_delete_private_dns_for_net;
+    resolv_get_private_dns_status_for_net;
+    resolv_register_private_dns_callback;
   local:
     *;
 };
diff --git a/resolv/res_init.cpp b/resolv/res_init.cpp
index cfb6f08..06d4452 100644
--- a/resolv/res_init.cpp
+++ b/resolv/res_init.cpp
@@ -158,10 +158,9 @@
     statp->pfcode = 0;
     statp->_vcsock = -1;
     statp->_flags = 0;
-    statp->qhook = NULL;
-    statp->rhook = NULL;
     statp->_u._ext.nscount = 0;
     statp->_u._ext.ext = (res_state_ext*) malloc(sizeof(*statp->_u._ext.ext));
+    statp->use_local_nameserver = false;
     if (statp->_u._ext.ext != NULL) {
         memset(statp->_u._ext.ext, 0, sizeof(*statp->_u._ext.ext));
         statp->_u._ext.ext->nsaddrs[0].sin = statp->nsaddr;
@@ -419,9 +418,11 @@
     if (statp != NULL) {
         statp->netid = netcontext->dns_netid;
         statp->_mark = netcontext->dns_mark;
-        statp->qhook = netcontext->qhook;
         if (netcontext->flags & NET_CONTEXT_FLAG_USE_EDNS) {
             statp->options |= RES_USE_EDNS0 | RES_USE_DNSSEC;
         }
+        if (netcontext->flags & NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS) {
+            statp->use_local_nameserver = true;
+        }
     }
 }
diff --git a/resolv/res_send.cpp b/resolv/res_send.cpp
index 5a79f28..4c0ca1a 100644
--- a/resolv/res_send.cpp
+++ b/resolv/res_send.cpp
@@ -100,6 +100,10 @@
 
 #include <android-base/logging.h>
 
+#include <netdutils/Slice.h>
+#include "netd_resolv/DnsTlsDispatcher.h"
+#include "netd_resolv/DnsTlsTransport.h"
+#include "netd_resolv/PrivateDnsConfiguration.h"
 #include "netd_resolv/resolv.h"
 #include "netd_resolv/stats.h"
 #include "private/android_filesystem_config.h"
@@ -107,6 +111,8 @@
 #include "resolv_cache.h"
 #include "resolv_private.h"
 
+// TODO: use the namespace something like android::netd_resolv for libnetd_resolv
+using namespace android::net;
 
 #define VLOG if (!kVerboseLogging) {} else LOG(INFO)
 
@@ -134,6 +140,10 @@
     }
 #endif  // DEBUG
 
+typedef enum { res_goahead, res_nextns, res_modified, res_done, res_error } res_sendhookact;
+
+static DnsTlsDispatcher sDnsTlsDispatcher;
+
 static int get_salen(const struct sockaddr*);
 static struct sockaddr* get_nsaddr(res_state, size_t);
 static int send_vc(res_state, struct __res_params* params, const u_char*, int, u_char*, int, int*,
@@ -146,6 +156,7 @@
 static int connect_with_timeout(int sock, const struct sockaddr* nsap, socklen_t salen,
                                 const struct timespec timeout);
 static int retrying_poll(const int sock, short events, const struct timespec* finish);
+static res_sendhookact res_tls_send(res_state, const Slice query, const Slice answer, int* resplen);
 
 /* BIONIC-BEGIN: implement source port randomization */
 
@@ -514,37 +525,40 @@
             statp->_flags |= (ns << RES_F_LASTSHIFT);
 
         same_ns:
-            if (statp->qhook) {
-                int done = 0, loops = 0;
+            int done = 0, loops = 0;
 
-                do {
-                    res_sendhookact act;
+            do {
+                res_sendhookact act;
 
-                    act = (*statp->qhook)(&nsap, &buf, &buflen, ans, anssiz, &resplen);
-                    switch (act) {
-                        case res_goahead:
-                            done = 1;
+                // TODO: This function tries to query on all of private DNS servers. Putting
+                // it here is strange since we should expect there is only one DNS server
+                // being queried. Consider moving it to other reasonable place. In addition,
+                // enum res_sendhookact can be removed.
+                act = res_tls_send(statp, Slice(const_cast<u_char*>(buf), buflen),
+                                   Slice(ans, anssiz), &resplen);
+                switch (act) {
+                    case res_goahead:
+                        done = 1;
+                        break;
+                    case res_nextns:
+                        res_nclose(statp);
+                        goto next_ns;
+                    case res_done:
+                        if (cache_status == RESOLV_CACHE_NOTFOUND) {
+                            _resolv_cache_add(statp->netid, buf, buflen, ans, resplen);
+                        }
+                        return (resplen);
+                    case res_modified:
+                        /* give the hook another try */
+                        if (++loops < 42) /*doug adams*/
                             break;
-                        case res_nextns:
-                            res_nclose(statp);
-                            goto next_ns;
-                        case res_done:
-                            if (cache_status == RESOLV_CACHE_NOTFOUND) {
-                                _resolv_cache_add(statp->netid, buf, buflen, ans, resplen);
-                            }
-                            return (resplen);
-                        case res_modified:
-                            /* give the hook another try */
-                            if (++loops < 42) /*doug adams*/
-                                break;
-                            [[fallthrough]];
-                        case res_error:
-                            [[fallthrough]];
-                        default:
-                            goto fail;
-                    }
-                } while (!done);
-            }
+                        [[fallthrough]];
+                    case res_error:
+                        [[fallthrough]];
+                    default:
+                        goto fail;
+                }
+            } while (!done);
 
             [[maybe_unused]] static const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
             [[maybe_unused]] char abuf[NI_MAXHOST];
@@ -620,33 +634,6 @@
                 (statp->options & RES_STAYOPEN) == 0U) {
                 res_nclose(statp);
             }
-            if (statp->rhook) {
-                int done = 0, loops = 0;
-
-                do {
-                    res_sendhookact act;
-
-                    act = (*statp->rhook)(nsap, buf, buflen, ans, anssiz, &resplen);
-                    switch (act) {
-                        case res_goahead:
-                        case res_done:
-                            done = 1;
-                            break;
-                        case res_nextns:
-                            res_nclose(statp);
-                            goto next_ns;
-                        case res_modified:
-                            /* give the hook another try */
-                            if (++loops < 42) /*doug adams*/
-                                break;
-                            [[fallthrough]];
-                        case res_error:
-                            [[fallthrough]];
-                        default:
-                            goto fail;
-                    }
-                } while (!done);
-            }
             return (resplen);
         next_ns:;
         }  // for each ns
@@ -1249,3 +1236,59 @@
             return 0;
     }
 }
+
+res_sendhookact res_tls_send(res_state statp, const Slice query, const Slice answer, int* resplen) {
+    const unsigned netId = statp->netid;
+    const unsigned mark = statp->_mark;
+
+    // TLS bypass
+    if (statp->use_local_nameserver) {
+        return res_goahead;
+    }
+
+    const auto privateDnsStatus = gPrivateDnsConfiguration.getStatus(netId);
+
+    if (privateDnsStatus.mode == PrivateDnsMode::OFF) return res_goahead;
+
+    if (privateDnsStatus.validatedServers.empty()) {
+        if (privateDnsStatus.mode == PrivateDnsMode::OPPORTUNISTIC) {
+            return res_goahead;
+        } else {
+            // Sleep and iterate some small number of times checking for the
+            // arrival of resolved and validated server IP addresses, instead
+            // of returning an immediate error.
+            using namespace std::chrono_literals;
+            std::this_thread::sleep_for(100ms);
+            return res_modified;
+        }
+    }
+
+    VLOG << __func__ << ": performing query over TLS";
+
+    const auto response = sDnsTlsDispatcher.query(privateDnsStatus.validatedServers, mark, query,
+                                                  answer, resplen);
+    if (response == DnsTlsTransport::Response::success) {
+        VLOG << __func__ << ": success";
+        return res_done;
+    }
+
+    VLOG << __func__ << ": abort: TLS query failed: " << static_cast<int>(response);
+
+    if (privateDnsStatus.mode == PrivateDnsMode::OPPORTUNISTIC) {
+        // In opportunistic mode, handle falling back to cleartext in some
+        // cases (DNS shouldn't fail if a validated opportunistic mode server
+        // becomes unreachable for some reason).
+        switch (response) {
+            case DnsTlsTransport::Response::network_error:
+            case DnsTlsTransport::Response::internal_error:
+                // Note: this will cause cleartext queries to be emitted, with
+                // all of the EDNS0 goodness enabled. Fingers crossed.  :-/
+                return res_goahead;
+            default:
+                break;
+        }
+    }
+
+    // There was an internal error.  Fail hard.
+    return res_error;
+}
diff --git a/resolv/res_state.cpp b/resolv/res_state.cpp
index 900c56a..325bb6e 100644
--- a/resolv/res_state.cpp
+++ b/resolv/res_state.cpp
@@ -132,10 +132,6 @@
     return rt ? rt->_nres : NULL;
 }
 
-void res_put_state(res_state /*res*/) {
-    /* nothing to do */
-}
-
 res_static* res_get_static(void) {
     _res_thread* rt = res_thread_get();
 
diff --git a/resolv/resolv_private.h b/resolv/resolv_private.h
index d4ee044..879eec7 100644
--- a/resolv/resolv_private.h
+++ b/resolv/resolv_private.h
@@ -126,8 +126,6 @@
         struct in_addr addr;
         uint32_t mask;
     } sort_list[MAXRESOLVSORT];
-    res_send_qhook qhook; /* query hook */
-    res_send_rhook rhook; /* response hook */
     int res_h_errno;      /* last one set for this context */
     unsigned _mark;       /* If non-0 SET_MARK to _mark on all request sockets */
     int _vcsock;          /* PRIVATE: for res_send VC i/o */
@@ -144,6 +142,7 @@
         } _ext;
     } _u;
     struct res_static rstatic[1];
+    bool use_local_nameserver; /* DNS-over-TLS bypass */
 };
 
 typedef struct __res_state* res_state;
@@ -236,7 +235,6 @@
 
 /* Things involving an internal (static) resolver context. */
 struct __res_state* res_get_state(void);
-void res_put_state(struct __res_state*);
 
 void res_close(void);
 int res_init(void);
diff --git a/server/Android.bp b/server/Android.bp
index 49ecba3..99164d8 100644
--- a/server/Android.bp
+++ b/server/Android.bp
@@ -98,7 +98,7 @@
         ":netd_metrics_aidl",
     ],
     shared_libs: [
-        "libbpf",
+        "libbpf_android",
         "libnetd_resolv",
         "libnetdaidl",
         "libbase",
@@ -130,7 +130,7 @@
         "android.system.net.netd@1.1",
         "libbase",
         "libbinder",
-        "libbpf",
+        "libbpf_android",
         "libcrypto",
         "libcutils",
         "libdl",
@@ -222,7 +222,7 @@
     shared_libs: [
         "libbase",
         "libbinder",
-        "libbpf",
+        "libbpf_android",
         "libcrypto",
         "libcutils",
         "liblog",
diff --git a/server/DnsProxyListener.cpp b/server/DnsProxyListener.cpp
index 8f8c38a..c205a4c 100644
--- a/server/DnsProxyListener.cpp
+++ b/server/DnsProxyListener.cpp
@@ -55,8 +55,6 @@
 #include "ResponseCode.h"
 #include "Stopwatch.h"
 #include "android/net/metrics/INetdEventListener.h"
-#include "netd_resolv/DnsTlsDispatcher.h"
-#include "netd_resolv/DnsTlsTransport.h"
 #include "thread_util.h"
 
 using android::String16;
@@ -110,90 +108,16 @@
     return true;
 }
 
-thread_local android_net_context thread_netcontext = {};
-
-DnsTlsDispatcher dnsTlsDispatcher;
-
-void catnap() {
-    using namespace std::chrono_literals;
-    std::this_thread::sleep_for(100ms);
-}
-
-res_sendhookact qhook(sockaddr* const * /*ns*/, const u_char** buf, int* buflen,
-                      u_char* ans, int anssiz, int* resplen) {
-    if (!thread_netcontext.qhook) {
-        ALOGE("qhook abort: thread qhook is null");
-        return res_goahead;
-    }
-    if (thread_netcontext.flags & NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS) {
-        return res_goahead;
-    }
-    if (!net::gCtls) {
-        ALOGE("qhook abort: gCtls is null");
-        return res_goahead;
-    }
-
-    const auto privateDnsStatus =
-            net::gCtls->resolverCtrl.getPrivateDnsStatus(thread_netcontext.dns_netid);
-
-    if (privateDnsStatus.mode == PrivateDnsMode::OFF) return res_goahead;
-
-    if (privateDnsStatus.validatedServers.empty()) {
-        if (privateDnsStatus.mode == PrivateDnsMode::OPPORTUNISTIC) {
-            return res_goahead;
-        } else {
-            // Sleep and iterate some small number of times checking for the
-            // arrival of resolved and validated server IP addresses, instead
-            // of returning an immediate error.
-            catnap();
-            return res_modified;
-        }
-    }
-
-    if (DBG) ALOGD("Performing query over TLS");
-
-    Slice query = netdutils::Slice(const_cast<u_char*>(*buf), *buflen);
-    Slice answer = netdutils::Slice(const_cast<u_char*>(ans), anssiz);
-    const auto response = dnsTlsDispatcher.query(
-            privateDnsStatus.validatedServers, thread_netcontext.dns_mark,
-            query, answer, resplen);
-    if (response == DnsTlsTransport::Response::success) {
-        if (DBG) ALOGD("qhook success");
-        return res_done;
-    }
-
-    if (DBG) {
-        ALOGW("qhook abort: TLS query failed: %d", (int)response);
-    }
-
-    if (privateDnsStatus.mode == PrivateDnsMode::OPPORTUNISTIC) {
-        // In opportunistic mode, handle falling back to cleartext in some
-        // cases (DNS shouldn't fail if a validated opportunistic mode server
-        // becomes unreachable for some reason).
-        switch (response) {
-            case DnsTlsTransport::Response::network_error:
-            case DnsTlsTransport::Response::internal_error:
-                // Note: this will cause cleartext queries to be emitted, with
-                // all of the EDNS0 goodness enabled. Fingers crossed.  :-/
-                return res_goahead;
-            default:
-                break;
-        }
-    }
-
-    // There was an internal error.  Fail hard.
-    return res_error;
-}
-
 constexpr bool requestingUseLocalNameservers(unsigned flags) {
     return (flags & NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS) != 0;
 }
 
 inline bool queryingViaTls(unsigned dns_netid) {
-    const auto privateDnsStatus = net::gCtls->resolverCtrl.getPrivateDnsStatus(dns_netid);
-    switch (privateDnsStatus.mode) {
+    ExternalPrivateDnsStatus privateDnsStatus = {PrivateDnsMode::OFF, 0, {}};
+    resolv_get_private_dns_status_for_net(dns_netid, &privateDnsStatus);
+    switch (static_cast<PrivateDnsMode>(privateDnsStatus.mode)) {
         case PrivateDnsMode::OPPORTUNISTIC:
-           return !privateDnsStatus.validatedServers.empty();
+            return privateDnsStatus.numServers != 0;
         case PrivateDnsMode::STRICT:
             return true;
         default:
@@ -230,16 +154,6 @@
             ctx->flags |= NET_CONTEXT_FLAG_USE_EDNS;
         }
     }
-
-    // Always set the qhook. An opportunistic mode server might have finished
-    // validating by the time the qhook runs. Note that this races with the
-    // queryingViaTls() check above, resulting in possibly sending queries over
-    // TLS without taking advantage of features like EDNS; c'est la guerre.
-    ctx->qhook = &qhook;
-
-    // Store the android_net_context instance in a thread_local variable
-    // so that the static qhook can access other fields of the struct.
-    thread_netcontext = *ctx;
 }
 
 }  // namespace
@@ -357,7 +271,7 @@
     Stopwatch s;
     maybeFixupNetContext(&mNetContext);
     const uid_t uid = mClient->getUid();
-    uint32_t rv = 0;
+    int32_t rv = 0;
     if (queryLimiter.start(uid)) {
         rv = android_getaddrinfofornetcontext(mHost, mService, mHints, &mNetContext, &result);
         queryLimiter.finish(uid);
@@ -408,13 +322,13 @@
             case INetdEventListener::REPORTING_LEVEL_METRICS:
                 // Metrics reporting is on. Send metrics.
                 mNetdEventListener->onDnsEvent(mNetContext.dns_netid,
-                                               INetdEventListener::EVENT_GETADDRINFO, (int32_t) rv,
+                                               INetdEventListener::EVENT_GETADDRINFO, rv,
                                                latencyMs, String16(""), {}, -1, -1);
                 break;
             case INetdEventListener::REPORTING_LEVEL_FULL:
                 // Full event info reporting is on. Send full info.
                 mNetdEventListener->onDnsEvent(mNetContext.dns_netid,
-                                               INetdEventListener::EVENT_GETADDRINFO, (int32_t) rv,
+                                               INetdEventListener::EVENT_GETADDRINFO, rv,
                                                latencyMs, String16(mHost), ip_addrs,
                                                total_ip_addr_count, mNetContext.uid);
                 break;
@@ -583,10 +497,12 @@
     maybeFixupNetContext(&mNetContext);
     const uid_t uid = mClient->getUid();
     struct hostent* hp = nullptr;
+    int32_t rv = 0;
     if (queryLimiter.start(uid)) {
-        hp = android_gethostbynamefornetcontext(mName, mAf, &mNetContext);
+        rv = android_gethostbynamefornetcontext(mName, mAf, &mNetContext, &hp);
         queryLimiter.finish(uid);
     } else {
+        rv = EAI_MEMORY;
         ALOGE("gethostbyname: from UID %d, max concurrent queries reached", uid);
     }
     const int latencyMs = lround(s.timeTaken());
@@ -600,6 +516,7 @@
 
     bool success = true;
     if (hp) {
+        // hp is not nullptr iff. rv is 0.
         success = mClient->sendCode(ResponseCode::DnsProxyQueryResult) == 0;
         success &= sendhostent(mClient, hp);
     } else {
@@ -638,13 +555,13 @@
                 // Metrics reporting is on. Send metrics.
                 mNetdEventListener->onDnsEvent(mNetContext.dns_netid,
                                                INetdEventListener::EVENT_GETHOSTBYNAME,
-                                               h_errno, latencyMs, String16(""), {}, -1, -1);
+                                               rv, latencyMs, String16(""), {}, -1, -1);
                 break;
             case INetdEventListener::REPORTING_LEVEL_FULL:
                 // Full event info reporting is on. Send full info.
                 mNetdEventListener->onDnsEvent(mNetContext.dns_netid,
                                                INetdEventListener::EVENT_GETHOSTBYNAME,
-                                               h_errno, latencyMs, String16(mName), ip_addrs,
+                                               rv, latencyMs, String16(mName), ip_addrs,
                                                total_ip_addr_count, uid);
                 break;
         }
diff --git a/server/ResolverController.cpp b/server/ResolverController.cpp
index a299120..0b326b0 100644
--- a/server/ResolverController.cpp
+++ b/server/ResolverController.cpp
@@ -38,6 +38,7 @@
 #include <android/net/INetd.h>
 #include <android/net/metrics/INetdEventListener.h>
 
+#include "Controllers.h"
 #include "DumpWriter.h"
 #include "EventReporter.h"
 #include "Fwmark.h"
@@ -45,18 +46,13 @@
 #include "Permission.h"
 #include "ResolverController.h"
 #include "ResolverStats.h"
-#include "netd_resolv/DnsTlsServer.h"
-#include "netd_resolv/DnsTlsTransport.h"
 #include "netd_resolv/params.h"
 #include "netd_resolv/resolv.h"
 #include "netd_resolv/stats.h"
-#include "netdutils/BackoffSequence.h"
 
 namespace android {
-
-using netdutils::BackoffSequence;
-
 namespace net {
+
 namespace {
 
 std::string addrToString(const sockaddr_storage* addr) {
@@ -66,324 +62,50 @@
     return std::string(out);
 }
 
-bool parseServer(const char* server, sockaddr_storage* parsed) {
-    addrinfo hints = {
-        .ai_family = AF_UNSPEC,
-        .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV
-    };
-    addrinfo* res;
-
-    int err = getaddrinfo(server, "853", &hints, &res);
-    if (err != 0) {
-        ALOGW("Failed to parse server address (%s): %s", server, gai_strerror(err));
-        return false;
-    }
-
-    memcpy(parsed, res->ai_addr, res->ai_addrlen);
-    freeaddrinfo(res);
-    return true;
-}
-
 const char* getPrivateDnsModeString(PrivateDnsMode mode) {
     switch (mode) {
-        case PrivateDnsMode::OFF: return "OFF";
-        case PrivateDnsMode::OPPORTUNISTIC: return "OPPORTUNISTIC";
-        case PrivateDnsMode::STRICT: return "STRICT";
+        case PrivateDnsMode::OFF:
+            return "OFF";
+        case PrivateDnsMode::OPPORTUNISTIC:
+            return "OPPORTUNISTIC";
+        case PrivateDnsMode::STRICT:
+            return "STRICT";
     }
 }
 
-constexpr const char* validationStatusToString(ResolverController::Validation value) {
+constexpr const char* validationStatusToString(Validation value) {
     switch (value) {
-        case ResolverController::Validation::in_process:     return "in_process";
-        case ResolverController::Validation::success:        return "success";
-        case ResolverController::Validation::fail:           return "fail";
-        case ResolverController::Validation::unknown_server: return "unknown_server";
-        case ResolverController::Validation::unknown_netid:  return "unknown_netid";
-        default: return "unknown_status";
+        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";
     }
 }
 
-
-class PrivateDnsConfiguration {
-  public:
-    typedef ResolverController::PrivateDnsStatus PrivateDnsStatus;
-    typedef ResolverController::Validation Validation;
-    typedef std::map<DnsTlsServer, Validation, AddressComparator> PrivateDnsTracker;
-
-    int set(uint32_t netId, uint32_t mark, const std::vector<std::string>& servers,
-            const std::string& name, const std::set<std::vector<uint8_t>>& fingerprints) {
+void onPrivateDnsValidation(unsigned netId, const char* server, const char* hostname,
+                            bool success) {
+    // Send a validation event to NetdEventListenerService.
+    const auto netdEventListener = net::gCtls->eventReporter.getNetdEventListener();
+    if (netdEventListener != nullptr) {
+        netdEventListener->onPrivateDnsValidationEvent(netId, android::String16(server),
+                                                       android::String16(hostname), success);
         if (DBG) {
-            ALOGD("PrivateDnsConfiguration::set(%u, 0x%x, %zu, %s, %zu)", netId, mark,
-                  servers.size(), name.c_str(), fingerprints.size());
+            ALOGD("Sending validation %s event on netId %u for %s with hostname %s",
+                  success ? "success" : "failure", netId, server, hostname);
         }
 
-        const bool explicitlyConfigured = !name.empty() || !fingerprints.empty();
-
-        // Parse the list of servers that has been passed in
-        std::set<DnsTlsServer> tlsServers;
-        for (size_t i = 0; i < servers.size(); ++i) {
-            sockaddr_storage parsed;
-            if (!parseServer(servers[i].c_str(), &parsed)) {
-                return -EINVAL;
-            }
-            DnsTlsServer server(parsed);
-            server.name = name;
-            server.fingerprints = fingerprints;
-            tlsServers.insert(server);
-        }
-
-        std::lock_guard guard(mPrivateDnsLock);
-        if (explicitlyConfigured) {
-            mPrivateDnsModes[netId] = PrivateDnsMode::STRICT;
-        } else if (!tlsServers.empty()) {
-            mPrivateDnsModes[netId] = PrivateDnsMode::OPPORTUNISTIC;
-        } else {
-            mPrivateDnsModes[netId] = PrivateDnsMode::OFF;
-            mPrivateDnsTransports.erase(netId);
-            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) {
-                ALOGE("Memory error while recording private DNS for netId %d", netId);
-                return -ENOMEM;
-            }
-        }
-        auto& tracker = netPair->second;
-
-        // 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 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);
-            }
-        }
-        return 0;
+    } else {
+        ALOGE("Validation event not sent since NetdEventListenerService is unavailable.");
     }
-
-    PrivateDnsStatus getStatus(unsigned netId) {
-        PrivateDnsStatus status{PrivateDnsMode::OFF, {}};
-
-        // This mutex is on the critical path of every DNS lookup.
-        //
-        // If the overhead of mutex acquisition proves too high, we could reduce
-        // it by maintaining an atomic_int32_t counter of TLS-enabled netids, or
-        // by using an RWLock.
-        std::lock_guard guard(mPrivateDnsLock);
-
-        const auto mode = mPrivateDnsModes.find(netId);
-        if (mode == mPrivateDnsModes.end()) return status;
-        status.mode = mode->second;
-
-        const auto netPair = mPrivateDnsTransports.find(netId);
-        if (netPair != mPrivateDnsTransports.end()) {
-            for (const auto& serverPair : netPair->second) {
-                if (serverPair.second == Validation::success) {
-                    status.validatedServers.push_back(serverPair.first);
-                }
-            }
-        }
-
-        return status;
-    }
-
-    void clear(unsigned netId) {
-        if (DBG) {
-            ALOGD("PrivateDnsConfiguration::clear(%u)", netId);
-        }
-        std::lock_guard guard(mPrivateDnsLock);
-        mPrivateDnsModes.erase(netId);
-        mPrivateDnsTransports.erase(netId);
-    }
-
-    void dump(DumpWriter& dw, unsigned netId) {
-        std::lock_guard guard(mPrivateDnsLock);
-
-        const auto& mode = mPrivateDnsModes.find(netId);
-        dw.println("Private DNS mode: %s", getPrivateDnsModeString(
-                (mode != mPrivateDnsModes.end()) ? mode->second : PrivateDnsMode::OFF));
-        const auto& netPair = mPrivateDnsTransports.find(netId);
-        if (netPair == mPrivateDnsTransports.end()) {
-            dw.println("No Private DNS servers configured");
-        } else {
-            const auto& tracker = netPair->second;
-            dw.println("Private DNS configuration (%zu entries)", tracker.size());
-            dw.incIndent();
-            for (const auto& kv : tracker) {
-                const auto& server = kv.first;
-                const auto status = kv.second;
-                dw.println("%s name{%s} status{%s}",
-                        addrToString(&(server.ss)).c_str(),
-                        server.name.c_str(),
-                        validationStatusToString(status));
-            }
-            dw.decIndent();
-        }
-    }
-
-  private:
-    void validatePrivateDnsProvider(const DnsTlsServer& server, PrivateDnsTracker& tracker,
-                                    unsigned netId, uint32_t mark) REQUIRES(mPrivateDnsLock) {
-        if (DBG) {
-            ALOGD("validatePrivateDnsProvider(%s, %u)", addrToString(&(server.ss)).c_str(), netId);
-        }
-
-        tracker[server] = Validation::in_process;
-        if (DBG) {
-            ALOGD("Server %s marked as in_process.  Tracker now has size %zu",
-                    addrToString(&(server.ss)).c_str(), tracker.size());
-        }
-        // Note that capturing |server| and |netId| in this lambda create copies.
-        std::thread validate_thread([this, server, netId, mark] {
-            // cat /proc/sys/net/ipv4/tcp_syn_retries yields "6".
-            //
-            // Start with a 1 minute delay and backoff to once per hour.
-            //
-            // Assumptions:
-            //     [1] Each TLS validation is ~10KB of certs+handshake+payload.
-            //     [2] Network typically provision clients with <=4 nameservers.
-            //     [3] Average month has 30 days.
-            //
-            // Each validation pass in a given hour is ~1.2MB of data. And 24
-            // such validation passes per day is about ~30MB per month, in the
-            // worst case. Otherwise, this will cost ~600 SYNs per month
-            // (6 SYNs per ip, 4 ips per validation pass, 24 passes per day).
-            auto backoff = BackoffSequence<>::Builder()
-                    .withInitialRetransmissionTime(std::chrono::seconds(60))
-                    .withMaximumRetransmissionTime(std::chrono::seconds(3600))
-                    .build();
-
-            while (true) {
-                // ::validate() is a blocking call that performs network operations.
-                // It can take milliseconds to minutes, up to the SYN retry limit.
-                const bool success = DnsTlsTransport::validate(server, netId, mark);
-                if (DBG) {
-                    ALOGD("validateDnsTlsServer returned %d for %s", success,
-                            addrToString(&(server.ss)).c_str());
-                }
-
-                const bool needs_reeval = this->recordPrivateDnsValidation(server, netId, success);
-                if (!needs_reeval) {
-                    break;
-                }
-
-                if (backoff.hasNextTimeout()) {
-                    std::this_thread::sleep_for(backoff.getNextTimeout());
-                } else {
-                    break;
-                }
-            }
-        });
-        validate_thread.detach();
-    }
-
-    bool recordPrivateDnsValidation(const DnsTlsServer& server, unsigned netId, bool success) {
-        constexpr bool NEEDS_REEVALUATION = true;
-        constexpr bool DONT_REEVALUATE = false;
-
-        std::lock_guard guard(mPrivateDnsLock);
-
-        auto netPair = mPrivateDnsTransports.find(netId);
-        if (netPair == mPrivateDnsTransports.end()) {
-            ALOGW("netId %u was erased during private DNS validation", netId);
-            return DONT_REEVALUATE;
-        }
-
-        const auto mode = mPrivateDnsModes.find(netId);
-        if (mode == mPrivateDnsModes.end()) {
-            ALOGW("netId %u has no private DNS validation mode", netId);
-            return DONT_REEVALUATE;
-        }
-        const bool modeDoesReevaluation = (mode->second == PrivateDnsMode::STRICT);
-
-        bool reevaluationStatus =
-                (success || !modeDoesReevaluation) ? DONT_REEVALUATE : NEEDS_REEVALUATION;
-
-        auto& tracker = netPair->second;
-        auto serverPair = tracker.find(server);
-        if (serverPair == tracker.end()) {
-            ALOGW("Server %s was removed during private DNS validation",
-                    addrToString(&(server.ss)).c_str());
-            success = false;
-            reevaluationStatus = DONT_REEVALUATE;
-        } else if (!(serverPair->first == server)) {
-            // TODO: It doesn't seem correct to overwrite the tracker entry for
-            // |server| down below in this circumstance... Fix this.
-            ALOGW("Server %s was changed during private DNS validation",
-                    addrToString(&(server.ss)).c_str());
-            success = false;
-            reevaluationStatus = DONT_REEVALUATE;
-        }
-
-        // Send a validation event to NetdEventListenerService.
-        if (mNetdEventListener == nullptr) {
-            mNetdEventListener = mEventReporter.getNetdEventListener();
-        }
-        if (mNetdEventListener != nullptr) {
-            const String16 ipLiteral(addrToString(&(server.ss)).c_str());
-            const String16 hostname(server.name.empty() ? "" : server.name.c_str());
-            mNetdEventListener->onPrivateDnsValidationEvent(netId, ipLiteral, hostname, success);
-            if (DBG) {
-                ALOGD("Sending validation %s event on netId %u for %s with hostname %s",
-                        success ? "success" : "failure", netId,
-                        addrToString(&(server.ss)).c_str(), server.name.c_str());
-            }
-        } else {
-            ALOGE("Validation event not sent since NetdEventListenerService is unavailable.");
-        }
-
-        if (success) {
-            tracker[server] = Validation::success;
-            if (DBG) {
-                ALOGD("Validation succeeded for %s! Tracker now has %zu entries.",
-                        addrToString(&(server.ss)).c_str(), tracker.size());
-            }
-        } 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;
-            if (DBG) {
-                ALOGD("Validation failed for %s!", addrToString(&(server.ss)).c_str());
-            }
-        }
-
-        return reevaluationStatus;
-    }
-
-    // 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.
-    static bool needsValidation(const PrivateDnsTracker& tracker, const DnsTlsServer& server) {
-        const auto& iter = tracker.find(server);
-        return (iter == tracker.end()) || (iter->second == Validation::fail);
-    }
-
-    EventReporter mEventReporter;
-
-    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.
-    std::map<unsigned, PrivateDnsTracker> mPrivateDnsTransports GUARDED_BY(mPrivateDnsLock);
-    android::sp<android::net::metrics::INetdEventListener>
-            mNetdEventListener GUARDED_BY(mPrivateDnsLock);
-} sPrivateDnsConfiguration;
+}
 
 bool allIPv6Only(const std::vector<std::string>& servers) {
     for (const auto& server : servers) {
@@ -402,18 +124,13 @@
     return -resolv_set_nameservers_for_net(netId, servers, numservers, searchDomains, params);
 }
 
-ResolverController::PrivateDnsStatus
-ResolverController::getPrivateDnsStatus(unsigned netId) const {
-    return sPrivateDnsConfiguration.getStatus(netId);
-}
-
 int ResolverController::clearDnsServers(unsigned netId) {
     resolv_set_nameservers_for_net(netId, nullptr, 0, "", nullptr);
     if (DBG) {
         ALOGD("clearDnsServers netId = %u\n", netId);
     }
     mDns64Configuration.stopPrefixDiscovery(netId);
-    sPrivateDnsConfiguration.clear(netId);
+    resolv_delete_private_dns_for_net(netId);
     return 0;
 }
 
@@ -512,6 +229,20 @@
         return -EINVAL;
     }
 
+    std::vector<const char*> server_ptrs;
+    size_t count = std::min<size_t>(MAXNS, tlsServers.size());
+    server_ptrs.reserve(count);
+    for (size_t i = 0; i < count; i++) {
+        server_ptrs.push_back(tlsServers[i].data());
+    }
+
+    std::vector<const uint8_t*> fingerprint_ptrs;
+    count = tlsFingerprints.size();
+    fingerprint_ptrs.reserve(count);
+    for (const auto& fp : tlsFingerprints) {
+        fingerprint_ptrs.push_back(fp.data());
+    }
+
     // At private DNS validation time, we only know the netId, so we have to guess/compute the
     // corresponding socket mark.
     Fwmark fwmark;
@@ -520,16 +251,19 @@
     fwmark.protectedFromVpn = true;
     fwmark.permission = PERMISSION_SYSTEM;
 
-    const int err = sPrivateDnsConfiguration.set(netId, fwmark.intValue, tlsServers, tlsName,
-                                                 tlsFingerprints);
+    const int err = resolv_set_private_dns_for_net(
+            netId, fwmark.intValue, server_ptrs.data(), server_ptrs.size(), tlsName.c_str(),
+            fingerprint_ptrs.data(), fingerprint_ptrs.size());
     if (err != 0) {
         return err;
     }
+    resolv_register_private_dns_callback(&onPrivateDnsValidation);
 
     // Convert network-assigned server list to bionic's format.
-    auto server_count = std::min<size_t>(MAXNS, servers.size());
-    std::vector<const char*> server_ptrs;
-    for (size_t i = 0 ; i < server_count ; ++i) {
+    server_ptrs.clear();
+    count = std::min<size_t>(MAXNS, servers.size());
+    server_ptrs.reserve(count);
+    for (size_t i = 0; i < count; ++i) {
         server_ptrs.push_back(servers[i].c_str());
     }
 
@@ -653,7 +387,24 @@
         }
 
         mDns64Configuration.dump(dw, netId);
-        sPrivateDnsConfiguration.dump(dw, netId);
+        ExternalPrivateDnsStatus privateDnsStatus = {PrivateDnsMode::OFF, 0, {}};
+        resolv_get_private_dns_status_for_net(netId, &privateDnsStatus);
+        dw.println("Private DNS mode: %s",
+                   getPrivateDnsModeString(static_cast<PrivateDnsMode>(privateDnsStatus.mode)));
+        if (!privateDnsStatus.numServers) {
+            dw.println("No Private DNS servers configured");
+        } else {
+            dw.println("Private DNS configuration (%u entries)", privateDnsStatus.numServers);
+            dw.incIndent();
+            for (int i = 0; i < privateDnsStatus.numServers; i++) {
+                dw.println("%s name{%s} status{%s}",
+                           addrToString(&(privateDnsStatus.serverStatus[i].ss)).c_str(),
+                           privateDnsStatus.serverStatus[i].hostname,
+                           validationStatusToString(static_cast<Validation>(
+                                   privateDnsStatus.serverStatus[i].validation)));
+            }
+            dw.decIndent();
+        }
     }
     dw.decIndent();
 }
diff --git a/server/ResolverController.h b/server/ResolverController.h
index a99bbee..ea95ee4 100644
--- a/server/ResolverController.h
+++ b/server/ResolverController.h
@@ -27,17 +27,9 @@
 namespace android {
 namespace net {
 
-struct DnsTlsServer;
 class DumpWriter;
 struct ResolverStats;
 
-enum class PrivateDnsMode {
-    OFF,
-    OPPORTUNISTIC,
-    STRICT,
-};
-
-
 class ResolverController {
   public:
     ResolverController(const NetworkController& netCtrl) : mDns64Configuration(netCtrl) {}
@@ -48,20 +40,6 @@
     int setDnsServers(unsigned netId, const char* searchDomains, const char** servers,
             int numservers, const __res_params* params);
 
-    // 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;
-        std::list<DnsTlsServer> validatedServers;
-    };
-
-    // Retrieve the Private DNS status for the given |netid|.
-    //
-    // If the requested |netid| is not known, the PrivateDnsStatus's mode has a
-    // default value of PrivateDnsMode::OFF, and validatedServers is empty.
-    PrivateDnsStatus getPrivateDnsStatus(unsigned netid) const;
-
     int clearDnsServers(unsigned netid);
 
     int flushDnsCache(unsigned netid);
diff --git a/tests/Android.bp b/tests/Android.bp
index a757e32..5b35fdc 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -62,7 +62,7 @@
         "libcap",
         "libnetd_test_dnsresponder",
         "libnetd_test_tun_interface",
-        "libbpf",
+        "libbpf_android",
         "liblogwrap",
         "libnetdaidl",
         "libnetdbpf",
diff --git a/tests/benchmarks/Android.bp b/tests/benchmarks/Android.bp
index 08a75e0..d2079c1 100644
--- a/tests/benchmarks/Android.bp
+++ b/tests/benchmarks/Android.bp
@@ -39,7 +39,7 @@
     defaults: ["netd_defaults"],
     shared_libs: [
         "libbase",
-        "libbpf",
+        "libbpf_android",
         "libnetdutils",
     ],
     static_libs: [