Merge "Fix typo in a few license files."
diff --git a/Android.bp b/Android.bp
index 908c389..1f634f2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -199,6 +199,8 @@
         "libnetd_resolv",
         "libnetd_test_dnsresponder",
         "libnetdutils",
+        "libprotobuf-cpp-lite",
         "server_configurable_flags",
+        "stats_proto",
     ],
 }
diff --git a/Dns64Configuration.cpp b/Dns64Configuration.cpp
index 12fb61f..7f43d34 100644
--- a/Dns64Configuration.cpp
+++ b/Dns64Configuration.cpp
@@ -33,9 +33,11 @@
 #include "DnsResolver.h"
 #include "getaddrinfo.h"
 #include "netd_resolv/resolv.h"
+#include "stats.pb.h"
 
 namespace android {
 
+using android::net::NetworkDnsEventReported;
 using netdutils::DumpWriter;
 using netdutils::IPAddress;
 using netdutils::IPPrefix;
@@ -154,7 +156,9 @@
     // ourselves, which means we also bypass all the special netcontext flag
     // handling and the resolver event logging.
     struct addrinfo* res = nullptr;
-    const int status = resolv_getaddrinfo(kIPv4OnlyHost, nullptr, &hints, &netcontext, &res);
+    NetworkDnsEventReported event;
+    const int status =
+            resolv_getaddrinfo(kIPv4OnlyHost, nullptr, &hints, &netcontext, &res, &event);
     ScopedAddrinfo result(res);
     if (status != 0) {
         LOG(WARNING) << "(" << cfg->netId << ", " << cfg->discoveryId << ") plat_prefix/dns("
diff --git a/DnsProxyListener.cpp b/DnsProxyListener.cpp
index 14a1566..2c6e7b6 100644
--- a/DnsProxyListener.cpp
+++ b/DnsProxyListener.cpp
@@ -299,17 +299,23 @@
     return true;
 }
 
+void initDnsEvent(NetworkDnsEventReported* event) {
+    // The value 0 has the special meaning of unset/unknown in Westworld atoms.
+    event->set_hints_ai_flags(-1);
+    event->set_res_nsend_flags(-1);
+}
+
 void reportDnsEvent(int eventType, const android_net_context& netContext, int latencyUs,
-                    int returnCode, const NetworkDnsEventReported& dnsEvent,
-                    const std::string& query_name, const std::vector<std::string>& ip_addrs = {},
-                    int total_ip_addr_count = 0) {
-    std::string dnsQueryStats = dnsEvent.dns_query_events().SerializeAsString();
-    char const* dnsQueryStatsBytes = dnsQueryStats.c_str();
-    stats::BytesField dnsQueryBytesField{dnsQueryStatsBytes, dnsQueryStats.size()};
-    android::net::stats::stats_write(android::net::stats::NETWORK_DNS_EVENT_REPORTED, eventType,
-                                     returnCode, latencyUs, dnsEvent.hints_ai_flags(),
-                                     dnsEvent.res_nsend_flags(), dnsEvent.network_type(),
-                                     dnsEvent.private_dns_modes(), dnsQueryBytesField);
+                    int returnCode, NetworkDnsEventReported& event, const std::string& query_name,
+                    const std::vector<std::string>& ip_addrs = {}, int total_ip_addr_count = 0) {
+    const std::string& dnsQueryStats = event.dns_query_events().SerializeAsString();
+    stats::BytesField dnsQueryBytesField{dnsQueryStats.c_str(), dnsQueryStats.size()};
+    event.set_return_code(static_cast<ReturnCode>(returnCode));
+    android::net::stats::stats_write(android::net::stats::NETWORK_DNS_EVENT_REPORTED,
+                                     event.event_type(), event.return_code(),
+                                     event.latency_micros(), event.hints_ai_flags(),
+                                     event.res_nsend_flags(), event.network_type(),
+                                     event.private_dns_modes(), dnsQueryBytesField);
 
     const auto& listeners = ResolverEventReporter::getInstance().getListeners();
     if (listeners.size() == 0) {
@@ -590,7 +596,8 @@
     return true;
 }
 
-void DnsProxyListener::GetAddrInfoHandler::doDns64Synthesis(int32_t* rv, addrinfo** res) {
+void DnsProxyListener::GetAddrInfoHandler::doDns64Synthesis(int32_t* rv, addrinfo** res,
+                                                            NetworkDnsEventReported* event) {
     if (mHost == nullptr) return;
 
     const bool ipv6WantedButNoData = (mHints && mHints->ai_family == AF_INET6 && *rv == EAI_NODATA);
@@ -613,7 +620,8 @@
             mHints->ai_family = AF_INET;
             // Don't need to do freeaddrinfo(res) before starting new DNS lookup because previous
             // DNS lookup is failed with error EAI_NODATA.
-            *rv = android_getaddrinfofornetcontext(mHost, mService, mHints, &mNetContext, res);
+            *rv = android_getaddrinfofornetcontext(mHost, mService, mHints, &mNetContext, res,
+                                                   event);
             queryLimiter.finish(uid);
             if (*rv) {
                 *rv = EAI_NODATA;  // return original error code
@@ -648,9 +656,10 @@
     maybeFixupNetContext(&mNetContext);
     const uid_t uid = mClient->getUid();
     int32_t rv = 0;
-    NetworkDnsEventReported dnsEvent;
+    NetworkDnsEventReported event;
+    initDnsEvent(&event);
     if (queryLimiter.start(uid)) {
-        rv = resolv_getaddrinfo(mHost, mService, mHints, &mNetContext, &result);
+        rv = resolv_getaddrinfo(mHost, mService, mHints, &mNetContext, &result, &event);
         queryLimiter.finish(uid);
     } else {
         // Note that this error code is currently not passed down to the client.
@@ -660,8 +669,11 @@
                    << ", max concurrent queries reached";
     }
 
-    doDns64Synthesis(&rv, &result);
-    const int latencyUs = int(s.timeTakenUs());
+    doDns64Synthesis(&rv, &result, &event);
+    const int32_t latencyUs = saturate_cast<int32_t>(s.timeTakenUs());
+    event.set_latency_micros(latencyUs);
+    event.set_event_type(EVENT_GETADDRINFO);
+    event.set_hints_ai_flags((mHints ? mHints->ai_flags : 0));
 
     if (rv) {
         // getaddrinfo failed
@@ -680,8 +692,8 @@
     }
     std::vector<std::string> ip_addrs;
     const int total_ip_addr_count = extractGetAddrInfoAnswers(result, &ip_addrs);
-    reportDnsEvent(INetdEventListener::EVENT_GETADDRINFO, mNetContext, latencyUs, rv, dnsEvent,
-                   mHost, ip_addrs, total_ip_addr_count);
+    reportDnsEvent(INetdEventListener::EVENT_GETADDRINFO, mNetContext, latencyUs, rv, event, mHost,
+                   ip_addrs, total_ip_addr_count);
     freeaddrinfo(result);
     mClient->decRef();
 }
@@ -854,10 +866,11 @@
     // Send DNS query
     std::vector<uint8_t> ansBuf(MAXPACKET, 0);
     int arcode, nsendAns = -1;
-    NetworkDnsEventReported dnsEvent;
+    NetworkDnsEventReported event;
+    initDnsEvent(&event);
     if (queryLimiter.start(uid)) {
         nsendAns = resolv_res_nsend(&mNetContext, msg.data(), msgLen, ansBuf.data(), MAXPACKET,
-                                    &arcode, static_cast<ResNsendFlags>(mFlags));
+                                    &arcode, static_cast<ResNsendFlags>(mFlags), &event);
         queryLimiter.finish(uid);
     } else {
         LOG(WARNING) << "ResNSendHandler::run: resnsend: from UID " << uid
@@ -865,14 +878,17 @@
         nsendAns = -EBUSY;
     }
 
-    const int latencyUs = int(s.timeTakenUs());
+    const int32_t latencyUs = saturate_cast<int32_t>(s.timeTakenUs());
+    event.set_latency_micros(latencyUs);
+    event.set_event_type(EVENT_RES_NSEND);
+    event.set_res_nsend_flags(static_cast<ResNsendFlags>(mFlags));
 
     // Fail, send -errno
     if (nsendAns < 0) {
         sendBE32(mClient, nsendAns);
         if (rr_type == ns_t_a || rr_type == ns_t_aaaa) {
             reportDnsEvent(INetdEventListener::EVENT_RES_NSEND, mNetContext, latencyUs,
-                           resNSendToAiError(nsendAns, arcode), dnsEvent, rr_name);
+                           resNSendToAiError(nsendAns, arcode), event, rr_name);
         }
         return;
     }
@@ -895,7 +911,7 @@
         const int total_ip_addr_count =
                 extractResNsendAnswers((uint8_t*) ansBuf.data(), nsendAns, rr_type, &ip_addrs);
         reportDnsEvent(INetdEventListener::EVENT_RES_NSEND, mNetContext, latencyUs,
-                       resNSendToAiError(nsendAns, arcode), dnsEvent, rr_name, ip_addrs,
+                       resNSendToAiError(nsendAns, arcode), event, rr_name, ip_addrs,
                        total_ip_addr_count);
     }
 }
@@ -994,7 +1010,8 @@
     free(mName);
 }
 
-void DnsProxyListener::GetHostByNameHandler::doDns64Synthesis(int32_t* rv, struct hostent** hpp) {
+void DnsProxyListener::GetHostByNameHandler::doDns64Synthesis(int32_t* rv, struct hostent** hpp,
+                                                              NetworkDnsEventReported* event) {
     // Don't have to consider family AF_UNSPEC case because gethostbyname{, 2} only supports
     // family AF_INET or AF_INET6.
     const bool ipv6WantedButNoData = (mAf == AF_INET6 && *rv == EAI_NODATA);
@@ -1011,7 +1028,7 @@
     // If caller wants IPv6 answers but no data, try to query IPv4 answers for synthesis
     const uid_t uid = mClient->getUid();
     if (queryLimiter.start(uid)) {
-        *rv = android_gethostbynamefornetcontext(mName, AF_INET, &mNetContext, hpp);
+        *rv = android_gethostbynamefornetcontext(mName, AF_INET, &mNetContext, hpp, event);
         queryLimiter.finish(uid);
         if (*rv) {
             *rv = EAI_NODATA;  // return original error code
@@ -1035,9 +1052,10 @@
     const uid_t uid = mClient->getUid();
     hostent* hp = nullptr;
     int32_t rv = 0;
-    NetworkDnsEventReported dnsEvent;
+    NetworkDnsEventReported event;
+    initDnsEvent(&event);
     if (queryLimiter.start(uid)) {
-        rv = android_gethostbynamefornetcontext(mName, mAf, &mNetContext, &hp);
+        rv = android_gethostbynamefornetcontext(mName, mAf, &mNetContext, &hp, &event);
         queryLimiter.finish(uid);
     } else {
         rv = EAI_MEMORY;
@@ -1045,8 +1063,11 @@
                    << ", max concurrent queries reached";
     }
 
-    doDns64Synthesis(&rv, &hp);
-    const int latencyUs = lround(s.timeTakenUs());
+    doDns64Synthesis(&rv, &hp, &event);
+    const int32_t latencyUs = saturate_cast<int32_t>(s.timeTakenUs());
+    event.set_latency_micros(latencyUs);
+    event.set_event_type(EVENT_GETHOSTBYNAME);
+
     LOG(DEBUG) << "GetHostByNameHandler::run: errno: " << (hp ? "success" : strerror(errno));
 
     bool success = true;
@@ -1064,7 +1085,7 @@
 
     std::vector<std::string> ip_addrs;
     const int total_ip_addr_count = extractGetHostByNameAnswers(hp, &ip_addrs);
-    reportDnsEvent(INetdEventListener::EVENT_GETHOSTBYNAME, mNetContext, latencyUs, rv, dnsEvent,
+    reportDnsEvent(INetdEventListener::EVENT_GETHOSTBYNAME, mNetContext, latencyUs, rv, event,
                    mName, ip_addrs, total_ip_addr_count);
     mClient->decRef();
 }
@@ -1134,7 +1155,8 @@
     free(mAddress);
 }
 
-void DnsProxyListener::GetHostByAddrHandler::doDns64ReverseLookup(struct hostent** hpp) {
+void DnsProxyListener::GetHostByAddrHandler::doDns64ReverseLookup(struct hostent** hpp,
+                                                                  NetworkDnsEventReported* event) {
     if (*hpp != nullptr || mAddressFamily != AF_INET6 || !mAddress) {
         return;
     }
@@ -1162,7 +1184,8 @@
     if (queryLimiter.start(uid)) {
         // Remove NAT64 prefix and do reverse DNS query
         struct in_addr v4addr = {.s_addr = v6addr.s6_addr32[3]};
-        android_gethostbyaddrfornetcontext(&v4addr, sizeof(v4addr), AF_INET, &mNetContext, hpp);
+        android_gethostbyaddrfornetcontext(&v4addr, sizeof(v4addr), AF_INET, &mNetContext, hpp,
+                                           event);
         queryLimiter.finish(uid);
         if (*hpp) {
             // Replace IPv4 address with original queried IPv6 address in place. The space has
@@ -1184,10 +1207,11 @@
     const uid_t uid = mClient->getUid();
     hostent* hp = nullptr;
     int32_t rv = 0;
-    NetworkDnsEventReported dnsEvent;
+    NetworkDnsEventReported event;
+    initDnsEvent(&event);
     if (queryLimiter.start(uid)) {
-        rv = android_gethostbyaddrfornetcontext(mAddress, mAddressLen, mAddressFamily,
-                                                &mNetContext, &hp);
+        rv = android_gethostbyaddrfornetcontext(mAddress, mAddressLen, mAddressFamily, &mNetContext,
+                                                &hp, &event);
         queryLimiter.finish(uid);
     } else {
         rv = EAI_MEMORY;
@@ -1195,8 +1219,10 @@
                    << ", max concurrent queries reached";
     }
 
-    doDns64ReverseLookup(&hp);
-    const int latencyUs = int(s.timeTakenUs());
+    doDns64ReverseLookup(&hp, &event);
+    const int32_t latencyUs = saturate_cast<int32_t>(s.timeTakenUs());
+    event.set_latency_micros(latencyUs);
+    event.set_event_type(EVENT_GETHOSTBYADDR);
 
     LOG(DEBUG) << "GetHostByAddrHandler::run: result: " << (hp ? "success" : gai_strerror(rv));
 
@@ -1212,7 +1238,7 @@
         LOG(WARNING) << "GetHostByAddrHandler::run: Error writing DNS result to client";
     }
 
-    reportDnsEvent(INetdEventListener::EVENT_GETHOSTBYADDR, mNetContext, latencyUs, rv, dnsEvent,
+    reportDnsEvent(INetdEventListener::EVENT_GETHOSTBYADDR, mNetContext, latencyUs, rv, event,
                    (hp && hp->h_name) ? hp->h_name : "null", {}, 0);
     mClient->decRef();
 }
diff --git a/DnsProxyListener.h b/DnsProxyListener.h
index 423e77f..9b71bc5 100644
--- a/DnsProxyListener.h
+++ b/DnsProxyListener.h
@@ -28,6 +28,8 @@
 namespace android {
 namespace net {
 
+class NetworkDnsEventReported;
+
 class DnsProxyListener : public FrameworkListener {
   public:
     DnsProxyListener();
@@ -54,7 +56,7 @@
         void run();
 
       private:
-        void doDns64Synthesis(int32_t* rv, addrinfo** res);
+        void doDns64Synthesis(int32_t* rv, addrinfo** res, NetworkDnsEventReported* event);
 
         SocketClient* mClient;  // ref counted
         char* mHost;            // owned. TODO: convert to std::string.
@@ -80,7 +82,7 @@
         void run();
 
       private:
-        void doDns64Synthesis(int32_t* rv, hostent** hpp);
+        void doDns64Synthesis(int32_t* rv, hostent** hpp, NetworkDnsEventReported* event);
 
         SocketClient* mClient;  // ref counted
         char* mName;            // owned. TODO: convert to std::string.
@@ -105,7 +107,7 @@
         void run();
 
       private:
-        void doDns64ReverseLookup(hostent** hpp);
+        void doDns64ReverseLookup(hostent** hpp, NetworkDnsEventReported* event);
 
         SocketClient* mClient;  // ref counted
         void* mAddress;         // address to lookup; owned
diff --git a/DnsTlsDispatcher.cpp b/DnsTlsDispatcher.cpp
index 957bd4f..d1c4022 100644
--- a/DnsTlsDispatcher.cpp
+++ b/DnsTlsDispatcher.cpp
@@ -17,13 +17,17 @@
 #define LOG_TAG "resolv"
 
 #include "DnsTlsDispatcher.h"
+#include <netdutils/Stopwatch.h>
 #include "DnsTlsSocketFactory.h"
+#include "resolv_private.h"
+#include "stats.pb.h"
 
 #include <android-base/logging.h>
 
 namespace android {
 namespace net {
 
+using android::netdutils::Stopwatch;
 using netdutils::Slice;
 
 // static
@@ -81,29 +85,45 @@
     return out;
 }
 
-DnsTlsTransport::Response DnsTlsDispatcher::query(
-        const std::list<DnsTlsServer> &tlsServers, unsigned mark,
-        const Slice query, const Slice ans, int *resplen) {
-    const std::list<DnsTlsServer> orderedServers(getOrderedServerList(tlsServers, mark));
+DnsTlsTransport::Response DnsTlsDispatcher::query(const std::list<DnsTlsServer>& tlsServers,
+                                                  res_state statp, const Slice query,
+                                                  const Slice ans, int* resplen) {
+    const std::list<DnsTlsServer> orderedServers(getOrderedServerList(tlsServers, statp->_mark));
 
     if (orderedServers.empty()) LOG(WARNING) << "Empty DnsTlsServer list";
 
     DnsTlsTransport::Response code = DnsTlsTransport::Response::internal_error;
+    int serverCount = 0;
     for (const auto& server : orderedServers) {
-        code = this->query(server, mark, query, ans, resplen);
+        DnsQueryEvent* dnsQueryEvent =
+                statp->event->mutable_dns_query_events()->add_dns_query_event();
+        dnsQueryEvent->set_rcode(NS_R_INTERNAL_ERROR);
+        Stopwatch query_stopwatch;
+        code = this->query(server, statp->_mark, query, ans, resplen);
+
+        dnsQueryEvent->set_latency_micros(saturate_cast<int32_t>(query_stopwatch.timeTakenUs()));
+        dnsQueryEvent->set_dns_server_index(serverCount++);
+        dnsQueryEvent->set_ip_version(ipFamilyToIPVersion(server.ss.ss_family));
+        dnsQueryEvent->set_protocol(PROTO_DOT);
+        dnsQueryEvent->set_type(getQueryType(query.base(), query.size()));
+
         switch (code) {
             // These response codes are valid responses and not expected to
             // change if another server is queried.
             case DnsTlsTransport::Response::success:
+                dnsQueryEvent->set_rcode(
+                        static_cast<NsRcode>(reinterpret_cast<HEADER*>(ans.base())->rcode));
+                [[fallthrough]];
             case DnsTlsTransport::Response::limit_error:
                 return code;
-                break;
             // These response codes might differ when trying other servers, so
             // keep iterating to see if we can get a different (better) result.
             case DnsTlsTransport::Response::network_error:
+                // Sync from res_tls_send in res_send.cpp
+                dnsQueryEvent->set_rcode(NS_R_TIMEOUT);
+                [[fallthrough]];
             case DnsTlsTransport::Response::internal_error:
                 continue;
-                break;
             // No "default" statement.
         }
     }
diff --git a/DnsTlsDispatcher.h b/DnsTlsDispatcher.h
index 7a48089..b8e9968 100644
--- a/DnsTlsDispatcher.h
+++ b/DnsTlsDispatcher.h
@@ -28,6 +28,7 @@
 #include "DnsTlsServer.h"
 #include "DnsTlsTransport.h"
 #include "IDnsTlsSocketFactory.h"
+#include "resolv_private.h"
 
 namespace android {
 namespace net {
@@ -48,9 +49,9 @@
     // the count of bytes written in |resplen|. Returns a success or error code.
     // The order in which servers from |tlsServers| are queried may not be the
     // order passed in by the caller.
-    DnsTlsTransport::Response query(const std::list<DnsTlsServer>& tlsServers, unsigned mark,
-                                    const netdutils::Slice query, const netdutils::Slice ans,
-                                    int* _Nonnull resplen);
+    DnsTlsTransport::Response query(const std::list<DnsTlsServer>& tlsServers,
+                                    res_state _Nonnull statp, const netdutils::Slice query,
+                                    const netdutils::Slice ans, int* _Nonnull resplen);
 
     // Given a |query|, sends it to the server on the network indicated by |mark|,
     // and writes the response into |ans|,  and indicates
diff --git a/PrivateDnsConfiguration.h b/PrivateDnsConfiguration.h
index 50fb54d..775cff1 100644
--- a/PrivateDnsConfiguration.h
+++ b/PrivateDnsConfiguration.h
@@ -30,7 +30,7 @@
 namespace net {
 
 // The DNS over TLS mode on a specific netId.
-enum class PrivateDnsMode : uint8_t { OFF, OPPORTUNISTIC, STRICT };
+enum class PrivateDnsMode : uint8_t { UNKNOWN, OFF, OPPORTUNISTIC, STRICT };
 
 // Validation status of a DNS over TLS server (on a specific netId).
 enum class Validation : uint8_t { in_process, success, fail, unknown_server, unknown_netid };
diff --git a/ResolverController.cpp b/ResolverController.cpp
index 2ceeba7..ceb7639 100644
--- a/ResolverController.cpp
+++ b/ResolverController.cpp
@@ -57,6 +57,8 @@
 
 const char* getPrivateDnsModeString(PrivateDnsMode mode) {
     switch (mode) {
+        case PrivateDnsMode::UNKNOWN:
+            return "UNKNOWN";
         case PrivateDnsMode::OFF:
             return "OFF";
         case PrivateDnsMode::OPPORTUNISTIC:
@@ -357,8 +359,7 @@
         mDns64Configuration.dump(dw, netId);
         ExternalPrivateDnsStatus privateDnsStatus = {PrivateDnsMode::OFF, 0, {}};
         gPrivateDnsConfiguration.getStatus(netId, &privateDnsStatus);
-        dw.println("Private DNS mode: %s",
-                   getPrivateDnsModeString(static_cast<PrivateDnsMode>(privateDnsStatus.mode)));
+        dw.println("Private DNS mode: %s", getPrivateDnsModeString(privateDnsStatus.mode));
         if (!privateDnsStatus.numServers) {
             dw.println("No Private DNS servers configured");
         } else {
diff --git a/getaddrinfo.cpp b/getaddrinfo.cpp
index 1758de5..e560ca5 100644
--- a/getaddrinfo.cpp
+++ b/getaddrinfo.cpp
@@ -62,6 +62,8 @@
 
 #define ANY 0
 
+using android::net::NetworkDnsEventReported;
+
 const char in_addrany[] = {0, 0, 0, 0};
 const char in_loopback[] = {127, 0, 0, 1};
 const char in6_addrany[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -124,7 +126,7 @@
 
 static int str2number(const char*);
 static int explore_fqdn(const struct addrinfo*, const char*, const char*, struct addrinfo**,
-                        const struct android_net_context*);
+                        const struct android_net_context*, NetworkDnsEventReported* event);
 static int explore_null(const struct addrinfo*, const char*, struct addrinfo**);
 static int explore_numeric(const struct addrinfo*, const char*, const char*, struct addrinfo**,
                            const char*);
@@ -140,7 +142,8 @@
 static struct addrinfo* getanswer(const querybuf*, int, const char*, int, const struct addrinfo*,
                                   int* herrno);
 static int dns_getaddrinfo(const char* name, const addrinfo* pai,
-                           const android_net_context* netcontext, addrinfo** rv);
+                           const android_net_context* netcontext, addrinfo** rv,
+                           NetworkDnsEventReported* event);
 static void _sethtent(FILE**);
 static void _endhtent(FILE**);
 static struct addrinfo* _gethtent(FILE**, const char*, const struct addrinfo*);
@@ -264,7 +267,9 @@
             .dns_mark = MARK_UNSET,
             .uid = NET_CONTEXT_INVALID_UID,
     };
-    return android_getaddrinfofornetcontext(hostname, servname, &hints, &netcontext, result);
+    NetworkDnsEventReported event;
+    return android_getaddrinfofornetcontext(hostname, servname, &hints, &netcontext, result,
+                                            &event);
 }
 
 namespace {
@@ -303,12 +308,13 @@
 
 int android_getaddrinfofornetcontext(const char* hostname, const char* servname,
                                      const addrinfo* hints, const android_net_context* netcontext,
-                                     addrinfo** res) {
+                                     addrinfo** res, NetworkDnsEventReported* event) {
     // hostname is allowed to be nullptr
     // servname is allowed to be nullptr
     // hints is allowed to be nullptr
     assert(res != nullptr);
     assert(netcontext != nullptr);
+    assert(event != nullptr);
 
     addrinfo sentinel = {};
     addrinfo* cur = &sentinel;
@@ -375,7 +381,7 @@
             break;
         }
 
-        return resolv_getaddrinfo(hostname, servname, hints, netcontext, res);
+        return resolv_getaddrinfo(hostname, servname, hints, netcontext, res, event);
     } while (0);
 
     if (error) {
@@ -388,7 +394,8 @@
 }
 
 int resolv_getaddrinfo(const char* _Nonnull hostname, const char* servname, const addrinfo* hints,
-                       const android_net_context* _Nonnull netcontext, addrinfo** _Nonnull res) {
+                       const android_net_context* _Nonnull netcontext, addrinfo** _Nonnull res,
+                       NetworkDnsEventReported* _Nonnull event) {
     if (hostname == nullptr && servname == nullptr) return EAI_NONAME;
     if (hostname == nullptr) return EAI_NODATA;
 
@@ -397,6 +404,7 @@
     // hints is allowed to be nullptr
     assert(res != nullptr);
     assert(netcontext != nullptr);
+    assert(event != nullptr);
 
     int error = EAI_FAIL;
     if (hints && (error = validateHints(hints))) {
@@ -423,7 +431,7 @@
 
         LOG(DEBUG) << __func__ << ": explore_fqdn(): ai_family=" << tmp.ai_family
                    << " ai_socktype=" << tmp.ai_socktype << " ai_protocol=" << tmp.ai_protocol;
-        error = explore_fqdn(&tmp, hostname, servname, &cur->ai_next, netcontext);
+        error = explore_fqdn(&tmp, hostname, servname, &cur->ai_next, netcontext, event);
 
         while (cur->ai_next) cur = cur->ai_next;
     }
@@ -437,7 +445,8 @@
 
 // FQDN hostname, DNS lookup
 static int explore_fqdn(const addrinfo* pai, const char* hostname, const char* servname,
-                        addrinfo** res, const android_net_context* netcontext) {
+                        addrinfo** res, const android_net_context* netcontext,
+                        NetworkDnsEventReported* event) {
     assert(pai != nullptr);
     // hostname may be nullptr
     // servname may be nullptr
@@ -450,7 +459,7 @@
     if ((error = get_portmatch(pai, servname))) return error;
 
     if (!files_getaddrinfo(hostname, pai, &result)) {
-        error = dns_getaddrinfo(hostname, pai, netcontext, &result);
+        error = dns_getaddrinfo(hostname, pai, netcontext, &result, event);
     }
     if (error) {
         freeaddrinfo(result);
@@ -1370,7 +1379,8 @@
 }
 
 static int dns_getaddrinfo(const char* name, const addrinfo* pai,
-                           const android_net_context* netcontext, addrinfo** rv) {
+                           const android_net_context* netcontext, addrinfo** rv,
+                           NetworkDnsEventReported* event) {
     res_target q = {};
     res_target q2 = {};
 
@@ -1432,7 +1442,7 @@
      * fully populate the thread private data here, but if we get down there
      * and have a cache hit that would be wasted, so we do the rest there on miss
      */
-    res_setnetcontext(res, netcontext);
+    res_setnetcontext(res, netcontext, event);
 
     int he;
     if (res_searchN(name, &q, res, &he) < 0) {
diff --git a/getaddrinfo.h b/getaddrinfo.h
index a601314..7f1c877 100644
--- a/getaddrinfo.h
+++ b/getaddrinfo.h
@@ -17,13 +17,15 @@
 #pragma once
 
 #include "netd_resolv/resolv.h"  // struct android_net_context
+#include "stats.pb.h"
 
 struct addrinfo;
 
 int android_getaddrinfofornetcontext(const char* hostname, const char* servname,
                                      const addrinfo* hints, const android_net_context* netcontext,
-                                     addrinfo** res);
+                                     addrinfo** res, android::net::NetworkDnsEventReported*);
 
 // This is the DNS proxy entry point for getaddrinfo().
 int resolv_getaddrinfo(const char* hostname, const char* servname, const addrinfo* hints,
-                       const android_net_context* netcontext, addrinfo** res);
+                       const android_net_context* netcontext, addrinfo** res,
+                       android::net::NetworkDnsEventReported*);
diff --git a/gethnamaddr.cpp b/gethnamaddr.cpp
index 9be5949..97236f9 100644
--- a/gethnamaddr.cpp
+++ b/gethnamaddr.cpp
@@ -77,6 +77,9 @@
 #include "netd_resolv/resolv.h"
 #include "resolv_cache.h"
 #include "resolv_private.h"
+#include "stats.pb.h"
+
+using android::net::NetworkDnsEventReported;
 
 // NetBSD uses _DIAGASSERT to null-check arguments and the like,
 // but it's clear from the number of mistakes in their assertions
@@ -113,19 +116,23 @@
 static void addrsort(char**, int, res_state);
 
 static int dns_gethtbyaddr(const unsigned char* uaddr, int len, int af,
-                           const android_net_context* netcontext, getnamaddr* info);
+                           const android_net_context* netcontext, getnamaddr* info,
+                           NetworkDnsEventReported* event);
 static int dns_gethtbyname(const char* name, int af, getnamaddr* info);
 
 static int gethostbyname_internal(const char* name, int af, res_state res, hostent* hp, char* hbuf,
-                                  size_t hbuflen, const android_net_context* netcontext);
+                                  size_t hbuflen, const android_net_context* netcontext,
+                                  NetworkDnsEventReported* event);
 static int gethostbyname_internal_real(const char* name, int af, hostent* hp, char* buf,
                                        size_t buflen);
+
 static int android_gethostbyaddrfornetcontext_proxy_internal(const void*, socklen_t, int,
                                                              struct hostent*, char*, size_t,
-                                                             const struct android_net_context*);
+                                                             const struct android_net_context*,
+                                                             NetworkDnsEventReported* event);
 static int android_gethostbyaddrfornetcontext_proxy(const void* addr, socklen_t len, int af,
                                                     const struct android_net_context* netcontext,
-                                                    hostent** hp);
+                                                    hostent** hp, NetworkDnsEventReported* event);
 
 #define BOUNDED_INCR(x)      \
     do {                     \
@@ -489,14 +496,16 @@
 
 // very similar in proxy-ness to android_getaddrinfo_proxy
 static int gethostbyname_internal(const char* name, int af, res_state res, hostent* hp, char* hbuf,
-                                  size_t hbuflen, const android_net_context* netcontext) {
-    res_setnetcontext(res, netcontext);
+                                  size_t hbuflen, const android_net_context* netcontext,
+                                  NetworkDnsEventReported* event) {
+    res_setnetcontext(res, netcontext, event);
     return gethostbyname_internal_real(name, af, hp, hbuf, hbuflen);
 }
 
 static int android_gethostbyaddrfornetcontext_real(const void* addr, socklen_t len, int af,
                                                    struct hostent* hp, char* buf, size_t buflen,
-                                                   const struct android_net_context* netcontext) {
+                                                   const struct android_net_context* netcontext,
+                                                   NetworkDnsEventReported* event) {
     const u_char* uaddr = (const u_char*) addr;
     socklen_t size;
     struct getnamaddr info;
@@ -538,7 +547,7 @@
     info.buf = buf;
     info.buflen = buflen;
     if (_hf_gethtbyaddr(uaddr, len, af, &info)) {
-        int error = dns_gethtbyaddr(uaddr, len, af, netcontext, &info);
+        int error = dns_gethtbyaddr(uaddr, len, af, netcontext, &info, event);
         if (error != 0) return error;
     }
     return 0;
@@ -546,8 +555,9 @@
 
 static int android_gethostbyaddrfornetcontext_proxy_internal(
         const void* addr, socklen_t len, int af, struct hostent* hp, char* hbuf, size_t hbuflen,
-        const struct android_net_context* netcontext) {
-    return android_gethostbyaddrfornetcontext_real(addr, len, af, hp, hbuf, hbuflen, netcontext);
+        const struct android_net_context* netcontext, NetworkDnsEventReported* event) {
+    return android_gethostbyaddrfornetcontext_real(addr, len, af, hp, hbuf, hbuflen, netcontext,
+                                                   event);
 }
 
 // TODO: Consider leaving function without returning error code as _gethtent() does because
@@ -758,7 +768,8 @@
 }
 
 static int dns_gethtbyaddr(const unsigned char* uaddr, int len, int af,
-                           const android_net_context* netcontext, getnamaddr* info) {
+                           const android_net_context* netcontext, getnamaddr* info,
+                           NetworkDnsEventReported* event) {
     char qbuf[MAXDNAME + 1], *qp, *ep;
     int n;
     int advance;
@@ -805,7 +816,7 @@
     res_state res = res_get_state();
     if (!res) return EAI_MEMORY;
 
-    res_setnetcontext(res, netcontext);
+    res_setnetcontext(res, netcontext, event);
     int he;
     n = res_nquery(res, qbuf, C_IN, T_PTR, buf->buf, (int)sizeof(buf->buf), &he);
     if (n < 0) {
@@ -843,13 +854,16 @@
  */
 
 int android_gethostbynamefornetcontext(const char* name, int af,
-                                       const struct android_net_context* netcontext, hostent** hp) {
-    int error;
+                                       const struct android_net_context* netcontext, hostent** hp,
+                                       NetworkDnsEventReported* event) {
+    assert(event != nullptr);
+
     res_state res = res_get_state();
     if (res == NULL) return EAI_MEMORY;
     res_static* rs = res_get_static();  // For thread-safety.
+    int error;
     error = gethostbyname_internal(name, af, res, &rs->host, rs->hostbuf, sizeof(rs->hostbuf),
-                                   netcontext);
+                                   netcontext, event);
     if (error == 0) {
         *hp = &rs->host;
     }
@@ -857,16 +871,19 @@
 }
 
 int android_gethostbyaddrfornetcontext(const void* addr, socklen_t len, int af,
-                                       const struct android_net_context* netcontext, hostent** hp) {
-    return android_gethostbyaddrfornetcontext_proxy(addr, len, af, netcontext, hp);
+                                       const struct android_net_context* netcontext, hostent** hp,
+                                       NetworkDnsEventReported* event) {
+    return android_gethostbyaddrfornetcontext_proxy(addr, len, af, netcontext, hp, event);
 }
 
 static int android_gethostbyaddrfornetcontext_proxy(const void* addr, socklen_t len, int af,
                                                     const struct android_net_context* netcontext,
-                                                    hostent** hp) {
+                                                    hostent** hp, NetworkDnsEventReported* event) {
+    assert(event != nullptr);
+
     struct res_static* rs = res_get_static();  // For thread-safety.
     int error = android_gethostbyaddrfornetcontext_proxy_internal(
-            addr, len, af, &rs->host, rs->hostbuf, sizeof(rs->hostbuf), netcontext);
+            addr, len, af, &rs->host, rs->hostbuf, sizeof(rs->hostbuf), netcontext, event);
     if (error == 0) *hp = &rs->host;
     return error;
 }
diff --git a/gethnamaddr.h b/gethnamaddr.h
index 27cc1c2..bfdb14e 100644
--- a/gethnamaddr.h
+++ b/gethnamaddr.h
@@ -18,10 +18,12 @@
 
 #include <netdb.h>               // struct hostent
 #include "netd_resolv/resolv.h"  // struct android_net_context
+#include "stats.pb.h"
 
 // This is the entry point for the gethostbyname() family of legacy calls.
-int android_gethostbynamefornetcontext(const char*, int, const android_net_context*, hostent**);
+int android_gethostbynamefornetcontext(const char*, int, const android_net_context*, hostent**,
+                                       android::net::NetworkDnsEventReported*);
 
 // This is the entry point for the gethostbyaddr() family of legacy calls.
 int android_gethostbyaddrfornetcontext(const void*, socklen_t, int, const android_net_context*,
-                                       hostent**);
+                                       hostent**, android::net::NetworkDnsEventReported*);
diff --git a/libnetd_resolv_test.cpp b/libnetd_resolv_test.cpp
index 6485e0d..4769b1f 100644
--- a/libnetd_resolv_test.cpp
+++ b/libnetd_resolv_test.cpp
@@ -27,6 +27,7 @@
 #include "getaddrinfo.h"
 #include "gethnamaddr.h"
 #include "resolv_cache.h"
+#include "stats.pb.h"
 
 #define NAME(variable) #variable
 
@@ -41,6 +42,7 @@
 namespace net {
 
 using android::base::StringPrintf;
+using android::net::NetworkDnsEventReported;
 using android::netdutils::ScopedAddrinfo;
 
 // Minimize class ResolverTest to be class TestBase because class TestBase doesn't need all member
@@ -122,8 +124,9 @@
     // Invalid hostname and servname.
     // Both hostname and servname are null pointers. Expect error number EAI_NONAME.
     struct addrinfo* result = nullptr;
+    NetworkDnsEventReported event;
     int rv = resolv_getaddrinfo(nullptr /*hostname*/, nullptr /*servname*/, nullptr /*hints*/,
-                                &mNetcontext, &result);
+                                &mNetcontext, &result, &event);
     EXPECT_EQ(EAI_NONAME, rv);
     if (result) {
         freeaddrinfo(result);
@@ -177,8 +180,9 @@
                 .ai_addr = config.ai_addr,
                 .ai_next = config.ai_next,
         };
-
-        rv = resolv_getaddrinfo("localhost", nullptr /*servname*/, &hints, &mNetcontext, &result);
+        NetworkDnsEventReported event;
+        rv = resolv_getaddrinfo("localhost", nullptr /*servname*/, &hints, &mNetcontext, &result,
+                                &event);
         EXPECT_EQ(config.expected_eai_error, rv);
 
         if (result) {
@@ -199,9 +203,9 @@
         const struct addrinfo hints = {
                 .ai_family = family,  // unsupported family
         };
-
+        NetworkDnsEventReported event;
         int rv = resolv_getaddrinfo("localhost", nullptr /*servname*/, &hints, &mNetcontext,
-                                    &result);
+                                    &result, &event);
         EXPECT_EQ(EAI_FAMILY, rv);
 
         if (result) freeaddrinfo(result);
@@ -246,9 +250,9 @@
                         .ai_protocol = protocol,
                         .ai_socktype = socktype,
                 };
-
+                NetworkDnsEventReported event;
                 int rv = resolv_getaddrinfo("localhost", nullptr /*servname*/, &hints, &mNetcontext,
-                                            &result);
+                                            &result, &event);
                 EXPECT_EQ(EAI_BADHINTS, rv);
 
                 if (result) freeaddrinfo(result);
@@ -319,7 +323,9 @@
         };
 
         struct addrinfo* result = nullptr;
-        int rv = resolv_getaddrinfo("localhost", config.servname, &hints, &mNetcontext, &result);
+        NetworkDnsEventReported event;
+        int rv = resolv_getaddrinfo("localhost", config.servname, &hints, &mNetcontext, &result,
+                                    &event);
         EXPECT_EQ(config.expected_eai_error, rv);
 
         if (result) freeaddrinfo(result);
@@ -342,7 +348,8 @@
     // Want AAAA answer but DNS server has A answer only.
     struct addrinfo* result = nullptr;
     const addrinfo hints = {.ai_family = AF_INET6};
-    int rv = resolv_getaddrinfo("v4only", nullptr, &hints, &mNetcontext, &result);
+    NetworkDnsEventReported event;
+    int rv = resolv_getaddrinfo("v4only", nullptr, &hints, &mNetcontext, &result, &event);
     EXPECT_LE(1U, GetNumQueries(dns, v4_host_name));
     EXPECT_EQ(nullptr, result);
     EXPECT_EQ(EAI_NODATA, rv);
@@ -380,7 +387,8 @@
 
         struct addrinfo* result = nullptr;
         const struct addrinfo hints = {.ai_family = config.ai_family};
-        int rv = resolv_getaddrinfo("sawadee", nullptr, &hints, &mNetcontext, &result);
+        NetworkDnsEventReported event;
+        int rv = resolv_getaddrinfo("sawadee", nullptr, &hints, &mNetcontext, &result, &event);
         EXPECT_EQ(0, rv);
         EXPECT_TRUE(result != nullptr);
         EXPECT_EQ(1U, GetNumQueries(dns, host_name));
@@ -427,7 +435,8 @@
 
             addrinfo* res = nullptr;
             const addrinfo hints = {.ai_family = family};
-            int rv = resolv_getaddrinfo(hostname, nullptr, &hints, &mNetcontext, &res);
+            NetworkDnsEventReported event;
+            int rv = resolv_getaddrinfo(hostname, nullptr, &hints, &mNetcontext, &res, &event);
             ScopedAddrinfo result(res);
             EXPECT_EQ(nullptr, result);
             EXPECT_EQ(EAI_FAIL, rv);
@@ -470,7 +479,8 @@
 
         struct addrinfo* result = nullptr;
         const struct addrinfo hints = {.ai_family = AF_UNSPEC};
-        int rv = resolv_getaddrinfo(host_name, nullptr, &hints, &mNetcontext, &result);
+        NetworkDnsEventReported event;
+        int rv = resolv_getaddrinfo(host_name, nullptr, &hints, &mNetcontext, &result, &event);
         EXPECT_EQ(config.expected_eai_error, rv);
 
         if (result) freeaddrinfo(result);
@@ -493,7 +503,8 @@
 
     struct addrinfo* result = nullptr;
     const struct addrinfo hints = {.ai_family = AF_UNSPEC};
-    int rv = resolv_getaddrinfo("hello", nullptr, &hints, &mNetcontext, &result);
+    NetworkDnsEventReported event;
+    int rv = resolv_getaddrinfo("hello", nullptr, &hints, &mNetcontext, &result, &event);
     EXPECT_EQ(NETD_RESOLV_TIMEOUT, rv);
 
     if (result) freeaddrinfo(result);
@@ -535,8 +546,8 @@
 
         addrinfo* res = nullptr;
         const addrinfo hints = {.ai_family = config.family};
-
-        int rv = resolv_getaddrinfo(config.name, nullptr, &hints, &mNetcontext, &res);
+        NetworkDnsEventReported event;
+        int rv = resolv_getaddrinfo(config.name, nullptr, &hints, &mNetcontext, &res, &event);
         ScopedAddrinfo result(res);
         EXPECT_EQ(nullptr, result);
         EXPECT_EQ(EAI_FAIL, rv);
@@ -592,7 +603,8 @@
 
             addrinfo* res = nullptr;
             const addrinfo hints = {.ai_family = family};
-            int rv = resolv_getaddrinfo(config.name, nullptr, &hints, &mNetcontext, &res);
+            NetworkDnsEventReported event;
+            int rv = resolv_getaddrinfo(config.name, nullptr, &hints, &mNetcontext, &res, &event);
             ScopedAddrinfo result(res);
             EXPECT_EQ(nullptr, result);
             EXPECT_EQ(EAI_FAIL, rv);
@@ -618,8 +630,8 @@
 
         addrinfo* res = nullptr;
         const addrinfo hints = {.ai_family = family};
-
-        int rv = resolv_getaddrinfo("hello", nullptr, &hints, &mNetcontext, &res);
+        NetworkDnsEventReported event;
+        int rv = resolv_getaddrinfo("hello", nullptr, &hints, &mNetcontext, &res, &event);
         ScopedAddrinfo result(res);
         EXPECT_EQ(nullptr, result);
         EXPECT_EQ(EAI_FAIL, rv);
@@ -655,8 +667,9 @@
         dns.clearQueries();
 
         struct hostent* hp = nullptr;
+        NetworkDnsEventReported event;
         int rv = android_gethostbynamefornetcontext("jiababuei", config.ai_family, &mNetcontext,
-                                                    &hp);
+                                                    &hp, &event);
         EXPECT_EQ(0, rv);
         EXPECT_TRUE(hp != nullptr);
         EXPECT_EQ(1U, GetNumQueries(dns, host_name));
@@ -700,7 +713,9 @@
             SCOPED_TRACE(StringPrintf("family: %d, config.name: %s", family, hostname));
 
             struct hostent* hp = nullptr;
-            int rv = android_gethostbynamefornetcontext(hostname, family, &mNetcontext, &hp);
+            NetworkDnsEventReported event;
+            int rv =
+                    android_gethostbynamefornetcontext(hostname, family, &mNetcontext, &hp, &event);
             EXPECT_EQ(nullptr, hp);
             EXPECT_EQ(EAI_FAIL, rv);
         }
@@ -722,7 +737,8 @@
 
     // Want AAAA answer but DNS server has A answer only.
     struct hostent* hp = nullptr;
-    int rv = android_gethostbynamefornetcontext("v4only", AF_INET6, &mNetcontext, &hp);
+    NetworkDnsEventReported event;
+    int rv = android_gethostbynamefornetcontext("v4only", AF_INET6, &mNetcontext, &hp, &event);
     EXPECT_LE(1U, GetNumQueries(dns, v4_host_name));
     EXPECT_EQ(nullptr, hp);
     EXPECT_EQ(EAI_NODATA, rv);
@@ -764,7 +780,8 @@
                                                     mDefaultSearchDomains, &mDefaultParams_Binder));
 
         struct hostent* hp = nullptr;
-        int rv = android_gethostbynamefornetcontext(host_name, AF_INET, &mNetcontext, &hp);
+        NetworkDnsEventReported event;
+        int rv = android_gethostbynamefornetcontext(host_name, AF_INET, &mNetcontext, &hp, &event);
         EXPECT_EQ(nullptr, hp);
         EXPECT_EQ(config.expected_eai_error, rv);
     }
@@ -785,7 +802,8 @@
                                                 mDefaultSearchDomains, &mDefaultParams_Binder));
 
     struct hostent* hp = nullptr;
-    int rv = android_gethostbynamefornetcontext(host_name, AF_INET, &mNetcontext, &hp);
+    NetworkDnsEventReported event;
+    int rv = android_gethostbynamefornetcontext(host_name, AF_INET, &mNetcontext, &hp, &event);
     EXPECT_EQ(NETD_RESOLV_TIMEOUT, rv);
 }
 
@@ -820,7 +838,9 @@
                 StringPrintf("config.family: %d, config.name: %s", config.family, config.name));
 
         struct hostent* hp = nullptr;
-        int rv = android_gethostbynamefornetcontext(config.name, config.family, &mNetcontext, &hp);
+        NetworkDnsEventReported event;
+        int rv = android_gethostbynamefornetcontext(config.name, config.family, &mNetcontext, &hp,
+                                                    &event);
         EXPECT_EQ(nullptr, hp);
         EXPECT_EQ(EAI_FAIL, rv);
     }
@@ -874,7 +894,9 @@
                     StringPrintf("family: %d, testHostName: %s", family, testHostName.c_str()));
 
             struct hostent* hp = nullptr;
-            int rv = android_gethostbynamefornetcontext(config.name, family, &mNetcontext, &hp);
+            NetworkDnsEventReported event;
+            int rv = android_gethostbynamefornetcontext(config.name, family, &mNetcontext, &hp,
+                                                        &event);
             EXPECT_EQ(nullptr, hp);
             EXPECT_EQ(EAI_FAIL, rv);
         }
@@ -898,7 +920,8 @@
         SCOPED_TRACE(StringPrintf("family: %d", family));
 
         struct hostent* hp = nullptr;
-        int rv = android_gethostbynamefornetcontext("hello", family, &mNetcontext, &hp);
+        NetworkDnsEventReported event;
+        int rv = android_gethostbynamefornetcontext("hello", family, &mNetcontext, &hp, &event);
         EXPECT_EQ(nullptr, hp);
         EXPECT_EQ(EAI_FAIL, rv);
     }
diff --git a/res_init.cpp b/res_init.cpp
index 5be0188..bb86723 100644
--- a/res_init.cpp
+++ b/res_init.cpp
@@ -309,7 +309,8 @@
     return (statp->nscount);
 }
 
-void res_setnetcontext(res_state statp, const struct android_net_context* netcontext) {
+void res_setnetcontext(res_state statp, const struct android_net_context* netcontext,
+                       android::net::NetworkDnsEventReported* _Nonnull event) {
     if (statp != NULL) {
         statp->netid = netcontext->dns_netid;
         statp->_mark = netcontext->dns_mark;
@@ -319,5 +320,6 @@
         if (netcontext->flags & NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS) {
             statp->use_local_nameserver = true;
         }
+        statp->event = event;
     }
 }
diff --git a/res_send.cpp b/res_send.cpp
index 3289378..ce6f36f 100644
--- a/res_send.cpp
+++ b/res_send.cpp
@@ -100,6 +100,7 @@
 #include <android/multinetwork.h>  // ResNsendFlags
 
 #include <netdutils/Slice.h>
+#include <netdutils/Stopwatch.h>
 #include "DnsTlsDispatcher.h"
 #include "DnsTlsTransport.h"
 #include "PrivateDnsConfiguration.h"
@@ -109,10 +110,13 @@
 #include "res_state_ext.h"
 #include "resolv_cache.h"
 #include "resolv_private.h"
+#include "stats.pb.h"
 
 // TODO: use the namespace something like android::netd_resolv for libnetd_resolv
 using namespace android::net;
+using android::net::NetworkDnsEventReported;
 using android::netdutils::Slice;
+using android::netdutils::Stopwatch;
 
 static DnsTlsDispatcher sDnsTlsDispatcher;
 
@@ -131,11 +135,29 @@
 static int res_tls_send(res_state, const Slice query, const Slice answer, int* rcode,
                         bool* fallback);
 
-/* BIONIC-BEGIN: implement source port randomization */
+NsType getQueryType(const uint8_t* msg, size_t msgLen) {
+    ns_msg handle;
+    ns_rr rr;
+    if (ns_initparse((const uint8_t*)msg, msgLen, &handle) < 0 ||
+        ns_parserr(&handle, ns_s_qd, 0, &rr) < 0) {
+        return NS_T_INVALID;
+    }
+    return static_cast<NsType>(ns_rr_type(rr));
+}
+
+IpVersion ipFamilyToIPVersion(const int ipFamily) {
+    switch (ipFamily) {
+        case AF_INET:
+            return IV_IPV4;
+        case AF_INET6:
+            return IV_IPV6;
+        default:
+            return IV_UNKNOWN;
+    }
+}
 
 // BEGIN: Code copied from ISC eventlib
 // TODO: move away from this code
-
 #define BILLION 1000000000
 
 static struct timespec evConsTime(time_t sec, long nsec) {
@@ -199,6 +221,7 @@
 
 // END: Code copied from ISC eventlib
 
+/* BIONIC-BEGIN: implement source port randomization */
 static int random_bind(int s, int family) {
     sockaddr_union u;
     int j;
@@ -371,6 +394,10 @@
     return (1);
 }
 
+static DnsQueryEvent* addDnsQueryEvent(NetworkDnsEventReported* event) {
+    return event->mutable_dns_query_events()->add_dns_query_event();
+}
+
 int res_nsend(res_state statp, const u_char* buf, int buflen, u_char* ans, int anssiz, int* rcode,
               uint32_t flags) {
     int gotsomewhere, terrno, v_circuit, resplen, n;
@@ -389,11 +416,15 @@
     terrno = ETIMEDOUT;
 
     int anslen = 0;
+    Stopwatch cache_stopwatch;
     cache_status = resolv_cache_lookup(statp->netid, buf, buflen, ans, anssiz, &anslen, flags);
-
+    const int32_t cacheLatencyUs = saturate_cast<int32_t>(cache_stopwatch.timeTakenUs());
     if (cache_status == RESOLV_CACHE_FOUND) {
         HEADER* hp = (HEADER*)(void*)ans;
         *rcode = hp->rcode;
+        DnsQueryEvent* dnsQueryEvent = addDnsQueryEvent(statp->event);
+        dnsQueryEvent->set_latency_micros(cacheLatencyUs);
+        dnsQueryEvent->set_cache_hit(static_cast<CacheStatus>(cache_status));
         return anslen;
     } else if (cache_status != RESOLV_CACHE_UNSUPPORTED) {
         // had a cache miss for a known network, so populate the thread private
@@ -492,12 +523,11 @@
 
         for (int ns = 0; ns < statp->nscount; ns++) {
             if (!usable_servers[ns]) continue;
-            struct sockaddr* nsap;
             int nsaplen;
             time_t now = 0;
             int delay = 0;
             *rcode = RCODE_INTERNAL_ERROR;
-            nsap = get_nsaddr(statp, (size_t) ns);
+            const sockaddr* nsap = get_nsaddr(statp, ns);
             nsaplen = get_salen(nsap);
 
         same_ns:
@@ -521,13 +551,16 @@
                 }
             }
 
-            [[maybe_unused]] static const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
-            [[maybe_unused]] char abuf[NI_MAXHOST];
+            static const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
+            char abuf[NI_MAXHOST];
+            DnsQueryEvent* dnsQueryEvent = addDnsQueryEvent(statp->event);
+            dnsQueryEvent->set_cache_hit(static_cast<CacheStatus>(cache_status));
 
             if (getnameinfo(nsap, (socklen_t)nsaplen, abuf, sizeof(abuf), NULL, 0, niflags) == 0)
                 LOG(DEBUG) << __func__ << ": Querying server (# " << ns + 1
                            << ") address = " << abuf;
 
+            Stopwatch query_stopwatch;
             if (v_circuit) {
                 /* Use VC; at most one attempt per server. */
                 bool shouldRecordStats = (attempt == 0);
@@ -536,6 +569,15 @@
                 n = send_vc(statp, &params, buf, buflen, ans, anssiz, &terrno, ns, &now, rcode,
                             &delay);
 
+                dnsQueryEvent->set_latency_micros(
+                        saturate_cast<int32_t>(query_stopwatch.timeTakenUs()));
+                dnsQueryEvent->set_dns_server_index(ns);
+                dnsQueryEvent->set_ip_version(ipFamilyToIPVersion(nsap->sa_family));
+                dnsQueryEvent->set_retry_times(attempt);
+                dnsQueryEvent->set_rcode(static_cast<NsRcode>(*rcode));
+                dnsQueryEvent->set_protocol(PROTO_TCP);
+                dnsQueryEvent->set_type(getQueryType(buf, buflen));
+
                 /*
                  * Only record stats the first time we try a query. This ensures that
                  * queries that deterministically fail (e.g., a name that always returns
@@ -564,6 +606,15 @@
                 n = send_dg(statp, &params, buf, buflen, ans, anssiz, &terrno, ns, &v_circuit,
                             &gotsomewhere, &now, rcode, &delay);
 
+                dnsQueryEvent->set_latency_micros(
+                        saturate_cast<int32_t>(query_stopwatch.timeTakenUs()));
+                dnsQueryEvent->set_dns_server_index(ns);
+                dnsQueryEvent->set_ip_version(ipFamilyToIPVersion(nsap->sa_family));
+                dnsQueryEvent->set_retry_times(attempt);
+                dnsQueryEvent->set_rcode(static_cast<NsRcode>(*rcode));
+                dnsQueryEvent->set_protocol(PROTO_UDP);
+                dnsQueryEvent->set_type(getQueryType(buf, buflen));
+
                 /* Only record stats the first time we try a query. See above. */
                 if (attempt == 0) {
                     res_sample sample;
@@ -1164,9 +1215,9 @@
                         bool* fallback) {
     int resplen = 0;
     const unsigned netId = statp->netid;
-    const unsigned mark = statp->_mark;
 
     PrivateDnsStatus privateDnsStatus = gPrivateDnsConfiguration.getStatus(netId);
+    statp->event->set_private_dns_modes(static_cast<PrivateDnsModes>(privateDnsStatus.mode));
 
     if (privateDnsStatus.mode == PrivateDnsMode::OFF) {
         *fallback = true;
@@ -1205,7 +1256,7 @@
 
     LOG(INFO) << __func__ << ": performing query over TLS";
 
-    const auto response = sDnsTlsDispatcher.query(privateDnsStatus.validatedServers, mark, query,
+    const auto response = sDnsTlsDispatcher.query(privateDnsStatus.validatedServers, statp, query,
                                                   answer, &resplen);
 
     LOG(INFO) << __func__ << ": TLS query result: " << static_cast<int>(response);
@@ -1247,9 +1298,11 @@
 }
 
 int resolv_res_nsend(const android_net_context* netContext, const uint8_t* msg, int msgLen,
-                     uint8_t* ans, int ansLen, int* rcode, uint32_t flags) {
+                     uint8_t* ans, int ansLen, int* rcode, uint32_t flags,
+                     NetworkDnsEventReported* event) {
+    assert(event != nullptr);
     res_state res = res_get_state();
-    res_setnetcontext(res, netContext);
+    res_setnetcontext(res, netContext, event);
     _resolv_populate_res_for_net(res);
     *rcode = NOERROR;
     return res_nsend(res, msg, msgLen, ans, ansLen, rcode, flags);
diff --git a/res_send.h b/res_send.h
index 7fc77cb..fb80160 100644
--- a/res_send.h
+++ b/res_send.h
@@ -17,7 +17,9 @@
 #pragma once
 
 #include "netd_resolv/resolv.h"  // struct android_net_context
+#include "stats.pb.h"
 
 // Query dns with raw msg
 int resolv_res_nsend(const android_net_context* netContext, const uint8_t* msg, int msgLen,
-                     uint8_t* ans, int ansLen, int* rcode, uint32_t flags);
+                     uint8_t* ans, int ansLen, int* rcode, uint32_t flags,
+                     android::net::NetworkDnsEventReported* event);
diff --git a/resolv_private.h b/resolv_private.h
index 41fc5de..6d58d15 100644
--- a/resolv_private.h
+++ b/resolv_private.h
@@ -64,6 +64,7 @@
 #include "netd_resolv/resolv.h"
 #include "netd_resolv/stats.h"
 #include "resolv_static.h"
+#include "stats.pb.h"
 
 // Linux defines MAXHOSTNAMELEN as 64, while the domain name limit in
 // RFC 1034 and RFC 1035 is 255 octets.
@@ -118,6 +119,7 @@
     } _u;
     struct res_static rstatic[1];
     bool use_local_nameserver; /* DNS-over-TLS bypass */
+    android::net::NetworkDnsEventReported* event;
 };
 
 typedef struct __res_state* res_state;
@@ -211,7 +213,8 @@
 int res_getservers(res_state, sockaddr_union*, int);
 
 struct android_net_context; /* forward */
-void res_setnetcontext(res_state, const struct android_net_context*);
+void res_setnetcontext(res_state, const struct android_net_context*,
+                       android::net::NetworkDnsEventReported* event);
 
 int getaddrinfo_numeric(const char* hostname, const char* servname, addrinfo hints,
                         addrinfo** result);
@@ -222,4 +225,16 @@
 // switch resolver log severity
 android::base::LogSeverity logSeverityStrToEnum(const std::string& logSeverityStr);
 
+template <typename Dest>
+Dest saturate_cast(int64_t x) {
+    using DestLimits = std::numeric_limits<Dest>;
+    if (x > DestLimits::max()) return DestLimits::max();
+    if (x < DestLimits::min()) return DestLimits::min();
+    return static_cast<Dest>(x);
+}
+
+android::net::NsType getQueryType(const uint8_t* msg, size_t msgLen);
+
+android::net::IpVersion ipFamilyToIPVersion(int ipFamily);
+
 #endif  // NETD_RESOLV_PRIVATE_H
diff --git a/stats.proto b/stats.proto
index d6fc577..7b48e92 100644
--- a/stats.proto
+++ b/stats.proto
@@ -68,6 +68,7 @@
     // NS_R_BADSIG  = 16,
     NS_R_BADKEY = 17;
     NS_R_BADTIME = 18;
+    NS_R_INTERNAL_ERROR = 254;
     NS_R_TIMEOUT = 255;
 }