Merge "Enable DNS resolver tests to be included in android-mts-dnsresolver as well as android-mts."
diff --git a/Android.bp b/Android.bp
index 5963362..9800f36 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,6 +27,8 @@
         "binder/android/net/ResolverHostsParcel.aidl",
         "binder/android/net/ResolverOptionsParcel.aidl",
         "binder/android/net/ResolverParamsParcel.aidl",
+        // New AIDL classes should go into android.net.resolv.aidl so they can be clearly identified
+        "binder/android/net/resolv/aidl/**/*.aidl",
     ],
     imports: [
         "netd_event_listener_interface",
@@ -54,6 +56,7 @@
         "4",
         "5",
         "6",
+        "7",
     ],
 }
 
diff --git a/DnsProxyListener.cpp b/DnsProxyListener.cpp
index d7fbcfc..2f3ebb0 100644
--- a/DnsProxyListener.cpp
+++ b/DnsProxyListener.cpp
@@ -62,6 +62,8 @@
 #include "stats.pb.h"
 
 using aidl::android::net::metrics::INetdEventListener;
+using aidl::android::net::resolv::aidl::DnsHealthEventParcel;
+using aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener;
 using android::net::NetworkDnsEventReported;
 
 namespace android {
@@ -347,16 +349,43 @@
     maybeLogQuery(eventType, netContext, event, query_name, ip_addrs);
 
     const auto& listeners = ResolverEventReporter::getInstance().getListeners();
-    if (listeners.size() == 0) {
+    if (listeners.empty()) {
         LOG(ERROR) << __func__
                    << ": DNS event not sent since no INetdEventListener receiver is available.";
-        return;
     }
     const int latencyMs = latencyUs / 1000;
     for (const auto& it : listeners) {
         it->onDnsEvent(netContext.dns_netid, eventType, returnCode, latencyMs, query_name, ip_addrs,
                        total_ip_addr_count, netContext.uid);
     }
+
+    const auto& unsolEventListeners = ResolverEventReporter::getInstance().getUnsolEventListeners();
+
+    if (returnCode == NETD_RESOLV_TIMEOUT) {
+        const DnsHealthEventParcel dnsHealthEvent = {
+                .netId = static_cast<int32_t>(netContext.dns_netid),
+                .healthResult = IDnsResolverUnsolicitedEventListener::DNS_HEALTH_RESULT_TIMEOUT,
+        };
+        for (const auto& it : unsolEventListeners) {
+            it->onDnsHealthEvent(dnsHealthEvent);
+        }
+    } else if (returnCode == NOERROR) {
+        DnsHealthEventParcel dnsHealthEvent = {
+                .netId = static_cast<int32_t>(netContext.dns_netid),
+                .healthResult = IDnsResolverUnsolicitedEventListener::DNS_HEALTH_RESULT_OK,
+        };
+        for (const auto& query : event.dns_query_events().dns_query_event()) {
+            if (query.cache_hit() != CS_FOUND && query.rcode() == NS_R_NO_ERROR) {
+                dnsHealthEvent.successRttMicros.push_back(query.latency_micros());
+            }
+        }
+
+        if (!dnsHealthEvent.successRttMicros.empty()) {
+            for (const auto& it : unsolEventListeners) {
+                it->onDnsHealthEvent(dnsHealthEvent);
+            }
+        }
+    }
 }
 
 bool onlyIPv4Answers(const addrinfo* res) {
diff --git a/DnsQueryLog.cpp b/DnsQueryLog.cpp
index 6f0e179..9cc3ca4 100644
--- a/DnsQueryLog.cpp
+++ b/DnsQueryLog.cpp
@@ -17,7 +17,7 @@
 
 #include "DnsQueryLog.h"
 
-#include <android-base/stringprintf.h>
+#include "util.h"
 
 namespace android::net {
 
@@ -45,25 +45,10 @@
     return ret.empty() ? "" : ret.substr(0, ret.length() - 2);
 }
 
-// Return the readable string format "hr:min:sec.ms".
-std::string timestampToString(const std::chrono::system_clock::time_point& ts) {
-    using std::chrono::duration_cast;
-    using std::chrono::milliseconds;
-    const auto time_sec = std::chrono::system_clock::to_time_t(ts);
-    char buf[32];
-    std::strftime(buf, sizeof(buf), "%H:%M:%S", std::localtime(&time_sec));
-    int ms = duration_cast<milliseconds>(ts.time_since_epoch()).count() % 1000;
-    return android::base::StringPrintf("%s.%03d", buf, ms);
-}
-
 }  // namespace
 
 void DnsQueryLog::push(Record&& record) {
-    std::lock_guard guard(mLock);
-    mQueue.push_back(std::move(record));
-    if (mQueue.size() > mCapacity) {
-        mQueue.pop_front();
-    }
+    mQueue.push(std::move(record));
 }
 
 void DnsQueryLog::dump(netdutils::DumpWriter& dw) const {
@@ -71,8 +56,7 @@
     netdutils::ScopedIndent indentStats(dw);
     const auto now = std::chrono::system_clock::now();
 
-    std::lock_guard guard(mLock);
-    for (const auto& record : mQueue) {
+    for (const auto& record : mQueue.copy()) {
         if (now - record.timestamp > mValidityTimeMs) continue;
 
         const std::string maskedHostname = maskHostname(record.hostname);
diff --git a/DnsQueryLog.h b/DnsQueryLog.h
index c19f8db..3e6478e 100644
--- a/DnsQueryLog.h
+++ b/DnsQueryLog.h
@@ -17,16 +17,16 @@
 
 #pragma once
 
-#include <deque>
 #include <string>
 #include <vector>
 
-#include <android-base/thread_annotations.h>
 #include <netdutils/DumpWriter.h>
 
+#include "LockedQueue.h"
+
 namespace android::net {
 
-// A circular buffer based class used for query logging. It's thread-safe for concurrent access.
+// This class stores query records in a locked ring buffer. It's thread-safe for concurrent access.
 class DnsQueryLog {
   public:
     static constexpr std::string_view DUMP_KEYWORD = "querylog";
@@ -52,15 +52,13 @@
     // Allow the tests to set the capacity and the validaty time in milliseconds.
     DnsQueryLog(size_t size = kDefaultLogSize,
                 std::chrono::milliseconds time = kDefaultValidityMinutes)
-        : mCapacity(size), mValidityTimeMs(time) {}
+        : mQueue(size), mValidityTimeMs(time) {}
 
-    void push(Record&& record) EXCLUDES(mLock);
-    void dump(netdutils::DumpWriter& dw) const EXCLUDES(mLock);
+    void push(Record&& record);
+    void dump(netdutils::DumpWriter& dw) const;
 
   private:
-    mutable std::mutex mLock;
-    std::deque<Record> mQueue GUARDED_BY(mLock);
-    const size_t mCapacity;
+    LockedRingBuffer<Record> mQueue;
     const std::chrono::milliseconds mValidityTimeMs;
 
     // The capacity of the circular buffer.
diff --git a/DnsResolver.cpp b/DnsResolver.cpp
index 5068aef..fcd4293 100644
--- a/DnsResolver.cpp
+++ b/DnsResolver.cpp
@@ -20,6 +20,8 @@
 
 #include "DnsProxyListener.h"
 #include "DnsResolverService.h"
+#include "DnsTlsDispatcher.h"
+#include "PrivateDnsConfiguration.h"
 #include "netd_resolv/resolv.h"
 #include "res_debug.h"
 #include "util.h"
@@ -28,7 +30,8 @@
     android::base::InitLogging(/*argv=*/nullptr);
     android::base::SetDefaultTag("libnetd_resolv");
     LOG(INFO) << __func__ << ": Initializing resolver";
-    resolv_set_log_severity(android::base::WARNING);
+    // TODO(b/170539625): restore log level to WARNING after clarifying flaky tests.
+    resolv_set_log_severity(isUserDebugBuild() ? android::base::DEBUG : android::base::WARNING);
     using android::net::gApiLevel;
     gApiLevel = getApiLevel();
     using android::net::gResNetdCallbacks;
@@ -72,6 +75,14 @@
     return &instance;
 }
 
+DnsResolver::DnsResolver() {
+    // TODO: make them member variables after fixing the circular dependency:
+    //   DnsTlsDispatcher.h -> resolv_private.h -> DnsResolver.h -> DnsTlsDispatcher.h
+    auto& dnsTlsDispatcher = DnsTlsDispatcher::getInstance();
+    auto& privateDnsConfiguration = PrivateDnsConfiguration::getInstance();
+    privateDnsConfiguration.setObserver(&dnsTlsDispatcher);
+}
+
 bool DnsResolver::start() {
     if (!verifyCallbacks()) {
         LOG(ERROR) << __func__ << ": Callback verification failed";
diff --git a/DnsResolver.h b/DnsResolver.h
index 863b096..9c2f3d8 100644
--- a/DnsResolver.h
+++ b/DnsResolver.h
@@ -40,7 +40,8 @@
     ResolverController resolverCtrl;
 
   private:
-    DnsResolver() {}
+    DnsResolver();
+
     DnsProxyListener mDnsProxyListener;
     DnsQueryLog mQueryLog;
 };
diff --git a/DnsResolverService.cpp b/DnsResolverService.cpp
index af449ab..899f726 100644
--- a/DnsResolverService.cpp
+++ b/DnsResolverService.cpp
@@ -32,6 +32,7 @@
 #include "DnsResolver.h"
 #include "Experiments.h"
 #include "NetdPermissions.h"  // PERM_*
+#include "PrivateDnsConfiguration.h"
 #include "ResolverEventReporter.h"
 #include "resolv_cache.h"
 
@@ -117,6 +118,8 @@
         gDnsResolv->resolverCtrl.dump(dw, netId);
         dw.blankline();
     }
+
+    PrivateDnsConfiguration::getInstance().dump(dw);
     Experiments::getInstance()->dump(dw);
     return STATUS_OK;
 }
@@ -138,6 +141,16 @@
     return statusFromErrcode(res);
 }
 
+::ndk::ScopedAStatus DnsResolverService::registerUnsolicitedEventListener(
+        const std::shared_ptr<
+                aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener>& listener) {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+
+    int res = ResolverEventReporter::getInstance().addUnsolEventListener(listener);
+
+    return statusFromErrcode(res);
+}
+
 ::ndk::ScopedAStatus DnsResolverService::checkAnyPermission(
         const std::vector<const char*>& permissions) {
     // TODO: Remove callback and move this to unnamed namespace after libbiner_ndk supports
diff --git a/DnsResolverService.h b/DnsResolverService.h
index 2228e21..fe39301 100644
--- a/DnsResolverService.h
+++ b/DnsResolverService.h
@@ -39,6 +39,10 @@
     ::ndk::ScopedAStatus registerEventListener(
             const std::shared_ptr<aidl::android::net::metrics::INetdEventListener>& listener)
             override;
+    ::ndk::ScopedAStatus registerUnsolicitedEventListener(
+            const std::shared_ptr<
+                    aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener>&
+                    listener) override;
 
     // Resolver commands.
     ::ndk::ScopedAStatus setResolverConfiguration(
diff --git a/DnsTlsDispatcher.h b/DnsTlsDispatcher.h
index c3dad06..2cb7519 100644
--- a/DnsTlsDispatcher.h
+++ b/DnsTlsDispatcher.h
@@ -28,6 +28,7 @@
 #include "DnsTlsServer.h"
 #include "DnsTlsTransport.h"
 #include "IDnsTlsSocketFactory.h"
+#include "PrivateDnsValidationObserver.h"
 #include "resolv_private.h"
 
 namespace android {
@@ -35,7 +36,7 @@
 
 // This is a singleton class that manages the collection of active DnsTlsTransports.
 // Queries made here are dispatched to an existing or newly constructed DnsTlsTransport.
-class DnsTlsDispatcher {
+class DnsTlsDispatcher : public PrivateDnsValidationObserver {
   public:
     // Constructor with dependency injection for testing.
     explicit DnsTlsDispatcher(std::unique_ptr<IDnsTlsSocketFactory> factory)
@@ -60,6 +61,9 @@
                                     const netdutils::Slice query, const netdutils::Slice ans,
                                     int* _Nonnull resplen, bool* _Nonnull connectTriggered);
 
+    // Implement PrivateDnsValidationObserver.
+    void onValidationStateUpdate(const std::string&, Validation, uint32_t) override{};
+
   private:
     DnsTlsDispatcher();
 
diff --git a/DnsTlsServer.h b/DnsTlsServer.h
index 6a72d88..67b0012 100644
--- a/DnsTlsServer.h
+++ b/DnsTlsServer.h
@@ -24,19 +24,11 @@
 
 #include <params.h>
 
+#include "PrivateDnsCommon.h"
+
 namespace android {
 namespace net {
 
-// Validation status of a DNS over TLS server (on a specific netId).
-enum class Validation : uint8_t {
-    in_process,
-    success,
-    success_but_expired,
-    fail,
-    unknown_server,
-    unknown_netid,
-};
-
 // DnsTlsServer represents a recursive resolver that supports, or may support, a
 // secure protocol.
 struct DnsTlsServer {
@@ -74,6 +66,12 @@
     Validation validationState() const { return mValidation; }
     void setValidationState(Validation val) { mValidation = val; }
 
+    // The socket mark used for validation.
+    // Note that the mark of a connection to which the DnsResolver sends app's DNS requests can
+    // be different.
+    // TODO: make it const.
+    uint32_t mark = 0;
+
     // Return whether or not the server can be used for a network. It depends on
     // the resolver configuration.
     bool active() const { return mActive; }
diff --git a/DnsTlsTransport.cpp b/DnsTlsTransport.cpp
index bcbb0a1..a2964cc 100644
--- a/DnsTlsTransport.cpp
+++ b/DnsTlsTransport.cpp
@@ -158,8 +158,8 @@
 // static
 // TODO: Use this function to preheat the session cache.
 // That may require moving it to DnsTlsDispatcher.
-bool DnsTlsTransport::validate(const DnsTlsServer& server, unsigned netid, uint32_t mark) {
-    LOG(DEBUG) << "Beginning validation on " << netid;
+bool DnsTlsTransport::validate(const DnsTlsServer& server, uint32_t mark) {
+    LOG(DEBUG) << "Beginning validation with mark " << std::hex << mark;
     // Generate "<random>-dnsotls-ds.metric.gstatic.com", which we will lookup through |ss| in
     // order to prove that it is actually a working DNS over TLS server.
     static const char kDnsSafeChars[] =
@@ -195,7 +195,7 @@
     DnsTlsTransport transport(server, mark, &factory);
     auto r = transport.query(netdutils::Slice(query, qlen)).get();
     if (r.code != Response::success) {
-        LOG(DEBUG) << "query failed";
+        LOG(WARNING) << "query failed";
         return false;
     }
 
@@ -212,7 +212,7 @@
     }
 
     const int ancount = (recvbuf[6] << 8) | recvbuf[7];
-    LOG(DEBUG) << netid << " answer count: " << ancount;
+    LOG(DEBUG) << "answer count: " << ancount;
 
     // TODO: Further validate the response contents (check for valid AAAA record, ...).
     // Note that currently, integration tests rely on this function accepting a
diff --git a/DnsTlsTransport.h b/DnsTlsTransport.h
index c0fbaef..7b7f81f 100644
--- a/DnsTlsTransport.h
+++ b/DnsTlsTransport.h
@@ -52,10 +52,10 @@
     // Given a |query|, this method sends it to the server and returns the result asynchronously.
     std::future<Result> query(const netdutils::Slice query) EXCLUDES(mLock);
 
-    // Check that a given TLS server is fully working on the specified netid.
+    // Check that a given TLS server is fully working with a specified mark.
     // This function is used in ResolverController to ensure that we don't enable DNS over TLS
     // on networks where it doesn't actually work.
-    static bool validate(const DnsTlsServer& server, unsigned netid, uint32_t mark);
+    static bool validate(const DnsTlsServer& server, uint32_t mark);
 
     int getConnectCounter() const EXCLUDES(mLock);
 
diff --git a/LockedQueue.h b/LockedQueue.h
index 0481eda..8694f2d 100644
--- a/LockedQueue.h
+++ b/LockedQueue.h
@@ -46,6 +46,30 @@
     std::deque<T> mQueue GUARDED_BY(mLock);
 };
 
+template <typename T>
+class LockedRingBuffer {
+  public:
+    explicit LockedRingBuffer(size_t size) : mCapacity(size) {}
+
+    void push(T&& record) {
+        std::lock_guard guard(mLock);
+        mQueue.push_back(std::move(record));
+        if (mQueue.size() > mCapacity) {
+            mQueue.pop_front();
+        }
+    }
+
+    std::deque<T> copy() const {
+        std::lock_guard guard(mLock);
+        return mQueue;
+    }
+
+  private:
+    mutable std::mutex mLock;
+    const size_t mCapacity;
+    std::deque<T> mQueue GUARDED_BY(mLock);
+};
+
 }  // end of namespace net
 }  // end of namespace android
 
diff --git a/PrivateDnsCommon.h b/PrivateDnsCommon.h
new file mode 100644
index 0000000..229e97c
--- /dev/null
+++ b/PrivateDnsCommon.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace android::net {
+
+// Validation status of a private DNS server on a specific netId.
+enum class Validation : uint8_t {
+    in_process,
+    success,
+    success_but_expired,
+    fail,
+    unknown_server,
+    unknown_netid,
+};
+
+// The private DNS mode on a specific netId.
+enum class PrivateDnsMode : uint8_t {
+    OFF,
+    OPPORTUNISTIC,
+    STRICT,
+};
+
+constexpr const char* validationStatusToString(Validation value) {
+    switch (value) {
+        case Validation::in_process:
+            return "in_process";
+        case Validation::success:
+            return "success";
+        case Validation::success_but_expired:
+            return "success_but_expired";
+        case Validation::fail:
+            return "fail";
+        case Validation::unknown_server:
+            return "unknown_server";
+        case Validation::unknown_netid:
+            return "unknown_netid";
+        default:
+            return "unknown_status";
+    }
+}
+
+constexpr const char* getPrivateDnsModeString(PrivateDnsMode mode) {
+    switch (mode) {
+        case PrivateDnsMode::OFF:
+            return "OFF";
+        case PrivateDnsMode::OPPORTUNISTIC:
+            return "OPPORTUNISTIC";
+        case PrivateDnsMode::STRICT:
+            return "STRICT";
+    }
+}
+
+}  // namespace android::net
diff --git a/PrivateDnsConfiguration.cpp b/PrivateDnsConfiguration.cpp
index 47969b3..c8b070a 100644
--- a/PrivateDnsConfiguration.cpp
+++ b/PrivateDnsConfiguration.cpp
@@ -18,6 +18,7 @@
 
 #include "PrivateDnsConfiguration.h"
 
+#include <android-base/format.h>
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <netdutils/ThreadUtil.h>
@@ -29,6 +30,8 @@
 #include "netdutils/BackoffSequence.h"
 #include "util.h"
 
+using aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener;
+using aidl::android::net::resolv::aidl::PrivateDnsValidationEventParcel;
 using android::base::StringPrintf;
 using android::netdutils::setThreadName;
 using std::chrono::milliseconds;
@@ -70,6 +73,7 @@
         DnsTlsServer server(parsed);
         server.name = name;
         server.certificate = caCert;
+        server.mark = mark;
         tmp[ServerIdentity(server)] = server;
     }
 
@@ -106,7 +110,7 @@
 
         if (needsValidation(server)) {
             updateServerState(identity, Validation::in_process, netId);
-            startValidation(server, netId, mark);
+            startValidation(server, netId);
         }
     }
 
@@ -140,10 +144,41 @@
     mPrivateDnsTransports.erase(netId);
 }
 
-void PrivateDnsConfiguration::startValidation(const DnsTlsServer& server, unsigned netId,
-                                              uint32_t mark) REQUIRES(mPrivateDnsLock) {
+bool PrivateDnsConfiguration::requestValidation(unsigned netId, const DnsTlsServer& server,
+                                                uint32_t mark) {
+    std::lock_guard guard(mPrivateDnsLock);
+    auto netPair = mPrivateDnsTransports.find(netId);
+    if (netPair == mPrivateDnsTransports.end()) {
+        return false;
+    }
+
+    auto& tracker = netPair->second;
+    const ServerIdentity identity = ServerIdentity(server);
+    auto it = tracker.find(identity);
+    if (it == tracker.end()) {
+        return false;
+    }
+
+    const DnsTlsServer& target = it->second;
+
+    if (!target.active()) return false;
+
+    if (target.validationState() != Validation::success) return false;
+
+    // Don't run the validation if |mark| (from android_net_context.dns_mark) is different.
+    // This is to protect validation from running on unexpected marks.
+    // Validation should be associated with a mark gotten by system permission.
+    if (target.mark != mark) return false;
+
+    updateServerState(identity, Validation::in_process, netId);
+    startValidation(target, netId);
+    return true;
+}
+
+void PrivateDnsConfiguration::startValidation(const DnsTlsServer& server, unsigned netId)
+        REQUIRES(mPrivateDnsLock) {
     // Note that capturing |server| and |netId| in this lambda create copies.
-    std::thread validate_thread([this, server, netId, mark] {
+    std::thread validate_thread([this, server, netId] {
         setThreadName(StringPrintf("TlsVerify_%u", netId).c_str());
 
         // cat /proc/sys/net/ipv4/tcp_syn_retries yields "6".
@@ -167,10 +202,11 @@
         while (true) {
             // ::validate() is a blocking call that performs network operations.
             // It can take milliseconds to minutes, up to the SYN retry limit.
-            LOG(WARNING) << "Validating DnsTlsServer on netId " << netId;
-            const bool success = DnsTlsTransport::validate(server, netId, mark);
-            LOG(DEBUG) << "validateDnsTlsServer returned " << success << " for "
-                       << server.toIpString();
+            LOG(WARNING) << "Validating DnsTlsServer " << server.toIpString() << " with mark 0x"
+                         << std::hex << server.mark;
+            const bool success = DnsTlsTransport::validate(server, server.mark);
+            LOG(WARNING) << "validateDnsTlsServer returned " << success << " for "
+                         << server.toIpString();
 
             const bool needs_reeval = this->recordPrivateDnsValidation(server, netId, success);
             if (!needs_reeval) {
@@ -188,6 +224,35 @@
     validate_thread.detach();
 }
 
+void PrivateDnsConfiguration::sendPrivateDnsValidationEvent(const DnsTlsServer& server,
+                                                            unsigned netId, bool success) {
+    LOG(DEBUG) << "Sending validation " << (success ? "success" : "failure") << " event on netId "
+               << netId << " for " << server.toIpString() << " with hostname {" << server.name
+               << "}";
+    // Send a validation event to NetdEventListenerService.
+    const auto& listeners = ResolverEventReporter::getInstance().getListeners();
+    if (listeners.empty()) {
+        LOG(ERROR)
+                << "Validation event not sent since no INetdEventListener receiver is available.";
+    }
+    for (const auto& it : listeners) {
+        it->onPrivateDnsValidationEvent(netId, server.toIpString(), server.name, success);
+    }
+
+    // Send a validation event to unsolicited event listeners.
+    const auto& unsolEventListeners = ResolverEventReporter::getInstance().getUnsolEventListeners();
+    const PrivateDnsValidationEventParcel validationEvent = {
+            .netId = static_cast<int32_t>(netId),
+            .ipAddress = server.toIpString(),
+            .hostname = server.name,
+            .validation = success ? IDnsResolverUnsolicitedEventListener::VALIDATION_RESULT_SUCCESS
+                                  : IDnsResolverUnsolicitedEventListener::VALIDATION_RESULT_FAILURE,
+    };
+    for (const auto& it : unsolEventListeners) {
+        it->onPrivateDnsValidationEvent(validationEvent);
+    }
+}
+
 bool PrivateDnsConfiguration::recordPrivateDnsValidation(const DnsTlsServer& server, unsigned netId,
                                                          bool success) {
     constexpr bool NEEDS_REEVALUATION = true;
@@ -199,14 +264,14 @@
     auto netPair = mPrivateDnsTransports.find(netId);
     if (netPair == mPrivateDnsTransports.end()) {
         LOG(WARNING) << "netId " << netId << " was erased during private DNS validation";
-        maybeNotifyObserver(identity.ip.toString(), Validation::fail, netId);
+        notifyValidationStateUpdate(identity.ip.toString(), Validation::fail, netId);
         return DONT_REEVALUATE;
     }
 
     const auto mode = mPrivateDnsModes.find(netId);
     if (mode == mPrivateDnsModes.end()) {
         LOG(WARNING) << "netId " << netId << " has no private DNS validation mode";
-        maybeNotifyObserver(identity.ip.toString(), Validation::fail, netId);
+        notifyValidationStateUpdate(identity.ip.toString(), Validation::fail, netId);
         return DONT_REEVALUATE;
     }
     const bool modeDoesReevaluation = (mode->second == PrivateDnsMode::STRICT);
@@ -234,19 +299,8 @@
         reevaluationStatus = DONT_REEVALUATE;
     }
 
-    // Send a validation event to NetdEventListenerService.
-    const auto& listeners = ResolverEventReporter::getInstance().getListeners();
-    if (listeners.size() != 0) {
-        for (const auto& it : listeners) {
-            it->onPrivateDnsValidationEvent(netId, server.toIpString(), server.name, success);
-        }
-        LOG(DEBUG) << "Sent validation " << (success ? "success" : "failure") << " event on netId "
-                   << netId << " for " << server.toIpString() << " with hostname {" << server.name
-                   << "}";
-    } else {
-        LOG(ERROR)
-                << "Validation event not sent since no INetdEventListener receiver is available.";
-    }
+    // Send private dns validation result to listeners.
+    sendPrivateDnsValidationEvent(server, netId, success);
 
     if (success) {
         updateServerState(identity, Validation::success, netId);
@@ -267,18 +321,21 @@
                                                 uint32_t netId) {
     auto netPair = mPrivateDnsTransports.find(netId);
     if (netPair == mPrivateDnsTransports.end()) {
-        maybeNotifyObserver(identity.ip.toString(), Validation::fail, netId);
+        notifyValidationStateUpdate(identity.ip.toString(), Validation::fail, netId);
         return;
     }
 
     auto& tracker = netPair->second;
     if (tracker.find(identity) == tracker.end()) {
-        maybeNotifyObserver(identity.ip.toString(), Validation::fail, netId);
+        notifyValidationStateUpdate(identity.ip.toString(), Validation::fail, netId);
         return;
     }
 
     tracker[identity].setValidationState(state);
-    maybeNotifyObserver(identity.ip.toString(), state, netId);
+    notifyValidationStateUpdate(identity.ip.toString(), state, netId);
+
+    RecordEntry record(netId, identity, state);
+    mPrivateDnsLog.push(std::move(record));
 }
 
 bool PrivateDnsConfiguration::needsValidation(const DnsTlsServer& server) {
@@ -297,17 +354,31 @@
     return false;
 }
 
-void PrivateDnsConfiguration::setObserver(Observer* observer) {
+void PrivateDnsConfiguration::setObserver(PrivateDnsValidationObserver* observer) {
     std::lock_guard guard(mPrivateDnsLock);
     mObserver = observer;
 }
 
-void PrivateDnsConfiguration::maybeNotifyObserver(const std::string& serverIp,
-                                                  Validation validation, uint32_t netId) const {
+void PrivateDnsConfiguration::notifyValidationStateUpdate(const std::string& serverIp,
+                                                          Validation validation,
+                                                          uint32_t netId) const {
     if (mObserver) {
         mObserver->onValidationStateUpdate(serverIp, validation, netId);
     }
 }
 
+void PrivateDnsConfiguration::dump(netdutils::DumpWriter& dw) const {
+    dw.println("PrivateDnsLog:");
+    netdutils::ScopedIndent indentStats(dw);
+
+    for (const auto& record : mPrivateDnsLog.copy()) {
+        dw.println(fmt::format("{} - netId={} PrivateDns={{{}/{}}} state={}",
+                               timestampToString(record.timestamp), record.netId,
+                               record.serverIdentity.ip.toString(), record.serverIdentity.name,
+                               validationStatusToString(record.state)));
+    }
+    dw.blankline();
+}
+
 }  // namespace net
 }  // namespace android
diff --git a/PrivateDnsConfiguration.h b/PrivateDnsConfiguration.h
index 722ed71..04f6a47 100644
--- a/PrivateDnsConfiguration.h
+++ b/PrivateDnsConfiguration.h
@@ -22,16 +22,16 @@
 #include <vector>
 
 #include <android-base/thread_annotations.h>
+#include <netdutils/DumpWriter.h>
 #include <netdutils/InternetAddresses.h>
 
 #include "DnsTlsServer.h"
+#include "LockedQueue.h"
+#include "PrivateDnsValidationObserver.h"
 
 namespace android {
 namespace net {
 
-// The DNS over TLS mode on a specific netId.
-enum class PrivateDnsMode : uint8_t { OFF, OPPORTUNISTIC, STRICT };
-
 struct PrivateDnsStatus {
     PrivateDnsMode mode;
 
@@ -65,6 +65,11 @@
 
     void clear(unsigned netId) EXCLUDES(mPrivateDnsLock);
 
+    // Request |server| to be revalidated on a connection tagged with |mark|.
+    // Return true if the request is accepted; otherwise, return false.
+    bool requestValidation(unsigned netId, const DnsTlsServer& server, uint32_t mark)
+            EXCLUDES(mPrivateDnsLock);
+
     struct ServerIdentity {
         const netdutils::IPAddress ip;
         const std::string name;
@@ -83,18 +88,24 @@
         }
     };
 
+    void setObserver(PrivateDnsValidationObserver* observer);
+
+    void dump(netdutils::DumpWriter& dw) const;
+
   private:
     typedef std::map<ServerIdentity, DnsTlsServer> PrivateDnsTracker;
     typedef std::set<DnsTlsServer, AddressComparator> ThreadTracker;
 
     PrivateDnsConfiguration() = default;
 
-    void startValidation(const DnsTlsServer& server, unsigned netId, uint32_t mark)
-            REQUIRES(mPrivateDnsLock);
+    void startValidation(const DnsTlsServer& server, unsigned netId) REQUIRES(mPrivateDnsLock);
 
     bool recordPrivateDnsValidation(const DnsTlsServer& server, unsigned netId, bool success)
             EXCLUDES(mPrivateDnsLock);
 
+    void sendPrivateDnsValidationEvent(const DnsTlsServer& server, unsigned netId, bool success)
+            REQUIRES(mPrivateDnsLock);
+
     // Decide if a validation for |server| is needed. Note that servers that have failed
     // multiple validation attempts but for which there is still a validating
     // thread running are marked as being Validation::in_process.
@@ -112,26 +123,25 @@
     // Any pending validation threads will continue running because we have no way to cancel them.
     std::map<unsigned, PrivateDnsTracker> mPrivateDnsTransports GUARDED_BY(mPrivateDnsLock);
 
-    // For testing. The observer is notified of onValidationStateUpdate 1) when a validation is
-    // about to begin or 2) when a validation finishes. If a validation finishes when in OFF mode
-    // or when the network has been destroyed, |validation| will be Validation::fail.
-    // WARNING: The Observer is notified while the lock is being held. Be careful not to call
-    // any method of PrivateDnsConfiguration from the observer.
+    void notifyValidationStateUpdate(const std::string& serverIp, Validation validation,
+                                     uint32_t netId) const REQUIRES(mPrivateDnsLock);
+
     // TODO: fix the reentrancy problem.
-    class Observer {
-      public:
-        virtual ~Observer(){};
-        virtual void onValidationStateUpdate(const std::string& serverIp, Validation validation,
-                                             uint32_t netId) = 0;
-    };
-
-    void setObserver(Observer* observer);
-    void maybeNotifyObserver(const std::string& serverIp, Validation validation,
-                             uint32_t netId) const REQUIRES(mPrivateDnsLock);
-
-    Observer* mObserver GUARDED_BY(mPrivateDnsLock);
+    PrivateDnsValidationObserver* mObserver GUARDED_BY(mPrivateDnsLock);
 
     friend class PrivateDnsConfigurationTest;
+
+    struct RecordEntry {
+        RecordEntry(uint32_t netId, const ServerIdentity& identity, Validation state)
+            : netId(netId), serverIdentity(identity), state(state) {}
+
+        const uint32_t netId;
+        const ServerIdentity serverIdentity;
+        const Validation state;
+        const std::chrono::system_clock::time_point timestamp = std::chrono::system_clock::now();
+    };
+
+    LockedRingBuffer<RecordEntry> mPrivateDnsLog{100};
 };
 
 }  // namespace net
diff --git a/PrivateDnsConfigurationTest.cpp b/PrivateDnsConfigurationTest.cpp
index f290277..8da3efb 100644
--- a/PrivateDnsConfigurationTest.cpp
+++ b/PrivateDnsConfigurationTest.cpp
@@ -60,7 +60,7 @@
     }
 
   protected:
-    class MockObserver : public PrivateDnsConfiguration::Observer {
+    class MockObserver : public PrivateDnsValidationObserver {
       public:
         MOCK_METHOD(void, onValidationStateUpdate,
                     (const std::string& serverIp, Validation validation, uint32_t netId),
@@ -254,6 +254,56 @@
     EXPECT_NE(ServerIdentity(server), ServerIdentity(other));
 }
 
+TEST_F(PrivateDnsConfigurationTest, RequestValidation) {
+    const DnsTlsServer server(netdutils::IPSockAddr::toIPSockAddr(kServer1, 853));
+
+    testing::InSequence seq;
+
+    for (const std::string_view config : {"SUCCESS", "IN_PROGRESS", "FAIL"}) {
+        SCOPED_TRACE(config);
+
+        EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
+        if (config == "SUCCESS") {
+            EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
+        } else if (config == "IN_PROGRESS") {
+            backend.setDeferredResp(true);
+        } else {
+            // config = "FAIL"
+            ASSERT_TRUE(backend.stopServer());
+            EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::fail, kNetId));
+        }
+        EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
+        expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
+
+        // Wait until the validation state is transitioned.
+        const int runningThreads = (config == "IN_PROGRESS") ? 1 : 0;
+        ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == runningThreads; }));
+
+        bool requestAccepted = false;
+        if (config == "SUCCESS") {
+            EXPECT_CALL(mObserver,
+                        onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
+            EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
+            requestAccepted = true;
+        } else if (config == "IN_PROGRESS") {
+            EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
+        }
+
+        EXPECT_EQ(mPdc.requestValidation(kNetId, server, kMark), requestAccepted);
+
+        // Resending the same request or requesting nonexistent servers are denied.
+        EXPECT_FALSE(mPdc.requestValidation(kNetId, server, kMark));
+        EXPECT_FALSE(mPdc.requestValidation(kNetId, server, kMark + 1));
+        EXPECT_FALSE(mPdc.requestValidation(kNetId + 1, server, kMark));
+
+        // Reset the test state.
+        backend.setDeferredResp(false);
+        backend.startServer();
+        ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
+        mPdc.clear(kNetId);
+    }
+}
+
 // TODO: add ValidationFail_Strict test.
 
 }  // namespace android::net
diff --git a/PrivateDnsValidationObserver.h b/PrivateDnsValidationObserver.h
new file mode 100644
index 0000000..b0cd5e2
--- /dev/null
+++ b/PrivateDnsValidationObserver.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "PrivateDnsCommon.h"
+
+namespace android::net {
+
+// The observer is notified of onValidationStateUpdate 1) when a validation is
+// about to begin or 2) when a validation finishes. If a validation finishes when in OFF mode
+// or when the network has been destroyed, |validation| will be Validation::fail.
+// WARNING: The Observer is notified while the lock is being held. Be careful not to call
+// any method of PrivateDnsConfiguration from the observer to cause reentrancy problem.
+class PrivateDnsValidationObserver {
+  public:
+    virtual ~PrivateDnsValidationObserver(){};
+    virtual void onValidationStateUpdate(const std::string& serverIp, Validation validation,
+                                         uint32_t netId) = 0;
+};
+
+}  // namespace android::net
diff --git a/ResolverController.cpp b/ResolverController.cpp
index 3a7f914..b193af1 100644
--- a/ResolverController.cpp
+++ b/ResolverController.cpp
@@ -35,6 +35,8 @@
 #include "stats.h"
 
 using aidl::android::net::ResolverParamsParcel;
+using aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener;
+using aidl::android::net::resolv::aidl::Nat64PrefixEventParcel;
 
 namespace android {
 
@@ -44,45 +46,31 @@
 
 namespace {
 
-const char* getPrivateDnsModeString(PrivateDnsMode mode) {
-    switch (mode) {
-        case PrivateDnsMode::OFF:
-            return "OFF";
-        case PrivateDnsMode::OPPORTUNISTIC:
-            return "OPPORTUNISTIC";
-        case PrivateDnsMode::STRICT:
-            return "STRICT";
-    }
-}
-
-constexpr const char* validationStatusToString(Validation value) {
-    switch (value) {
-        case Validation::in_process:
-            return "in_process";
-        case Validation::success:
-            return "success";
-        case Validation::success_but_expired:
-            return "success_but_expired";
-        case Validation::fail:
-            return "fail";
-        case Validation::unknown_server:
-            return "unknown_server";
-        case Validation::unknown_netid:
-            return "unknown_netid";
-        default:
-            return "unknown_status";
-    }
-}
-
 void sendNat64PrefixEvent(const Dns64Configuration::Nat64PrefixInfo& args) {
+    LOG(DEBUG) << "Sending Nat64Prefix " << (args.added ? "added" : "removed") << " event on netId "
+               << args.netId << " with address {" << args.prefixString << "(" << args.prefixLength
+               << ")}";
+    // Send a nat64 prefix event to NetdEventListenerService.
     const auto& listeners = ResolverEventReporter::getInstance().getListeners();
-    if (listeners.size() == 0) {
-        LOG(ERROR) << __func__ << ": No available listener. dropping NAT64 prefix event";
-        return;
+    if (listeners.empty()) {
+        LOG(ERROR) << __func__ << ": No available listener. Skipping NAT64 prefix event";
     }
     for (const auto& it : listeners) {
         it->onNat64PrefixEvent(args.netId, args.added, args.prefixString, args.prefixLength);
     }
+
+    const auto& unsolEventListeners = ResolverEventReporter::getInstance().getUnsolEventListeners();
+    const Nat64PrefixEventParcel nat64PrefixEvent = {
+            .netId = static_cast<int32_t>(args.netId),
+            .prefixOperation =
+                    args.added ? IDnsResolverUnsolicitedEventListener::PREFIX_OPERATION_ADDED
+                               : IDnsResolverUnsolicitedEventListener::PREFIX_OPERATION_REMOVED,
+            .prefixAddress = args.prefixString,
+            .prefixLength = args.prefixLength,
+    };
+    for (const auto& it : unsolEventListeners) {
+        it->onNat64PrefixEvent(nat64PrefixEvent);
+    }
 }
 
 int getDnsInfo(unsigned netId, std::vector<std::string>* servers, std::vector<std::string>* domains,
diff --git a/ResolverEventReporter.cpp b/ResolverEventReporter.cpp
index b4e9b5c..a809c88 100644
--- a/ResolverEventReporter.cpp
+++ b/ResolverEventReporter.cpp
@@ -21,6 +21,7 @@
 #include <android/binder_manager.h>
 
 using aidl::android::net::metrics::INetdEventListener;
+using aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener;
 
 ResolverEventReporter& ResolverEventReporter::getInstance() {
     // It should be initialized only once.
@@ -38,10 +39,19 @@
     return getListenersImpl();
 }
 
+ResolverEventReporter::UnsolEventListenerSet ResolverEventReporter::getUnsolEventListeners() const {
+    return getUnsolEventListenersImpl();
+}
+
 int ResolverEventReporter::addListener(const std::shared_ptr<INetdEventListener>& listener) {
     return addListenerImpl(listener);
 }
 
+int ResolverEventReporter::addUnsolEventListener(
+        const std::shared_ptr<IDnsResolverUnsolicitedEventListener>& listener) {
+    return addUnsolEventListenerImpl(listener);
+}
+
 // TODO: Consider registering metrics listener from framework and remove this function.
 // Currently, the framework listener "netd_listener" is shared by netd and libnetd_resolv.
 // Consider breaking it into two listeners. Once it has done, may let framework register
@@ -73,11 +83,28 @@
     if (found != mListeners.end()) mListeners.erase(found);
 }
 
+void ResolverEventReporter::handleUnsolEventBinderDied(const void* who) {
+    std::lock_guard lock(mMutex);
+
+    // Use the raw binder pointer address to be the identification of dead binder. Treat "who"
+    // which passes the raw address of dead binder as an identification only.
+    auto found = std::find_if(mUnsolEventListeners.begin(), mUnsolEventListeners.end(),
+                              [=](const auto& it) { return static_cast<void*>(it.get()) == who; });
+
+    if (found != mUnsolEventListeners.end()) mUnsolEventListeners.erase(found);
+}
+
 ResolverEventReporter::ListenerSet ResolverEventReporter::getListenersImpl() const {
     std::lock_guard lock(mMutex);
     return mListeners;
 }
 
+ResolverEventReporter::UnsolEventListenerSet ResolverEventReporter::getUnsolEventListenersImpl()
+        const {
+    std::lock_guard lock(mMutex);
+    return mUnsolEventListeners;
+}
+
 int ResolverEventReporter::addListenerImpl(const std::shared_ptr<INetdEventListener>& listener) {
     std::lock_guard lock(mMutex);
     return addListenerImplLocked(listener);
@@ -126,4 +153,56 @@
 
     mListeners.insert(listener);
     return 0;
-}
\ No newline at end of file
+}
+
+int ResolverEventReporter::addUnsolEventListenerImpl(
+        const std::shared_ptr<IDnsResolverUnsolicitedEventListener>& listener) {
+    std::lock_guard lock(mMutex);
+    return addUnsolEventListenerImplLocked(listener);
+}
+
+int ResolverEventReporter::addUnsolEventListenerImplLocked(
+        const std::shared_ptr<IDnsResolverUnsolicitedEventListener>& listener) {
+    if (listener == nullptr) {
+        LOG(ERROR) << "The unsolicited event listener should not be null";
+        return -EINVAL;
+    }
+
+    for (const auto& it : mUnsolEventListeners) {
+        if (it->asBinder().get() == listener->asBinder().get()) {
+            LOG(WARNING) << "The unsolicited event listener was already subscribed";
+            return -EEXIST;
+        }
+    }
+
+    static AIBinder_DeathRecipient* deathRecipient = nullptr;
+    if (deathRecipient == nullptr) {
+        // The AIBinder_DeathRecipient object is used to manage all death recipients for multiple
+        // binder objects. It doesn't released because there should have at least one binder object
+        // from framework.
+        // TODO: Considering to remove death recipient for the binder object from framework because
+        // it doesn't need death recipient actually.
+        deathRecipient = AIBinder_DeathRecipient_new([](void* cookie) {
+            ResolverEventReporter::getInstance().handleUnsolEventBinderDied(cookie);
+        });
+    }
+
+    // Pass the raw binder pointer address to be the cookie of the death recipient. While the death
+    // notification is fired, the cookie is used for identifying which binder was died. Because
+    // the NDK binder doesn't pass dead binder pointer to binder death handler, the binder death
+    // handler can't know who was died via wp<IBinder>. The reason for wp<IBinder> is not passed
+    // is that NDK binder can't transform a wp<IBinder> to a wp<AIBinder> in some cases.
+    // See more information in b/128712772.
+    auto binder = listener->asBinder().get();
+    auto cookie = static_cast<void*>(listener.get());  // Used for dead binder identification.
+    binder_status_t status = AIBinder_linkToDeath(binder, deathRecipient, cookie);
+
+    if (STATUS_OK != status) {
+        LOG(ERROR)
+                << "Failed to register death notification for IDnsResolverUnsolicitedEventListener";
+        return -EAGAIN;
+    }
+
+    mUnsolEventListeners.insert(listener);
+    return 0;
+}
diff --git a/ResolverEventReporter.h b/ResolverEventReporter.h
index eb0d9cf..39663b7 100644
--- a/ResolverEventReporter.h
+++ b/ResolverEventReporter.h
@@ -22,6 +22,7 @@
 #include <android-base/thread_annotations.h>
 
 #include "aidl/android/net/metrics/INetdEventListener.h"
+#include "aidl/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.h"
 
 /*
  * This class can be used to get the binder reference to the netd events listener service
@@ -36,6 +37,8 @@
     ResolverEventReporter& operator=(ResolverEventReporter&&) = delete;
 
     using ListenerSet = std::set<std::shared_ptr<aidl::android::net::metrics::INetdEventListener>>;
+    using UnsolEventListenerSet = std::set<std::shared_ptr<
+            aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener>>;
 
     // Get the instance of the singleton ResolverEventReporter.
     static ResolverEventReporter& getInstance();
@@ -43,10 +46,20 @@
     // Return the binder from the singleton ResolverEventReporter. This method is threadsafe.
     ListenerSet getListeners() const;
 
+    // Return registered binder services from the singleton ResolverEventReporter. This method is
+    // threadsafe.
+    UnsolEventListenerSet getUnsolEventListeners() const;
+
     // Add the binder to the singleton ResolverEventReporter. This method is threadsafe.
     int addListener(
             const std::shared_ptr<aidl::android::net::metrics::INetdEventListener>& listener);
 
+    // Add the binder to the singleton ResolverEventReporter. This method is threadsafe.
+    int addUnsolEventListener(
+            const std::shared_ptr<
+                    aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener>&
+                    listener);
+
   private:
     ResolverEventReporter() = default;
     ~ResolverEventReporter() = default;
@@ -58,11 +71,22 @@
     int addListenerImplLocked(
             const std::shared_ptr<aidl::android::net::metrics::INetdEventListener>& listener)
             REQUIRES(mMutex);
+    int addUnsolEventListenerImpl(
+            const std::shared_ptr<
+                    aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener>&
+                    listener) EXCLUDES(mMutex);
+    int addUnsolEventListenerImplLocked(
+            const std::shared_ptr<
+                    aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener>&
+                    listener) REQUIRES(mMutex);
     ListenerSet getListenersImpl() const EXCLUDES(mMutex);
+    UnsolEventListenerSet getUnsolEventListenersImpl() const EXCLUDES(mMutex);
     void handleBinderDied(const void* who) EXCLUDES(mMutex);
+    void handleUnsolEventBinderDied(const void* who) EXCLUDES(mMutex);
 
     mutable std::mutex mMutex;
     ListenerSet mListeners GUARDED_BY(mMutex);
+    UnsolEventListenerSet mUnsolEventListeners GUARDED_BY(mMutex);
 };
 
 #endif  // NETD_RESOLV_EVENT_REPORTER_H
diff --git a/aidl_api/dnsresolver_aidl_interface/7/.hash b/aidl_api/dnsresolver_aidl_interface/7/.hash
new file mode 100644
index 0000000..75f2e38
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/.hash
@@ -0,0 +1 @@
+a1dc9394598357ccaa74e96f564e7f248b72bad2
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/IDnsResolver.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/IDnsResolver.aidl
new file mode 100644
index 0000000..1f80545
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/IDnsResolver.aidl
@@ -0,0 +1,66 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface IDnsResolver {
+  boolean isAlive();
+  void registerEventListener(android.net.metrics.INetdEventListener listener);
+  void setResolverConfiguration(in android.net.ResolverParamsParcel resolverParams);
+  void getResolverInfo(int netId, out @utf8InCpp String[] servers, out @utf8InCpp String[] domains, out @utf8InCpp String[] tlsServers, out int[] params, out int[] stats, out int[] wait_for_pending_req_timeout_count);
+  void startPrefix64Discovery(int netId);
+  void stopPrefix64Discovery(int netId);
+  @utf8InCpp String getPrefix64(int netId);
+  void createNetworkCache(int netId);
+  void destroyNetworkCache(int netId);
+  void setLogSeverity(int logSeverity);
+  void flushNetworkCache(int netId);
+  void setPrefix64(int netId, @utf8InCpp String prefix);
+  void registerUnsolicitedEventListener(android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener listener);
+  const int RESOLVER_PARAMS_SAMPLE_VALIDITY = 0;
+  const int RESOLVER_PARAMS_SUCCESS_THRESHOLD = 1;
+  const int RESOLVER_PARAMS_MIN_SAMPLES = 2;
+  const int RESOLVER_PARAMS_MAX_SAMPLES = 3;
+  const int RESOLVER_PARAMS_BASE_TIMEOUT_MSEC = 4;
+  const int RESOLVER_PARAMS_RETRY_COUNT = 5;
+  const int RESOLVER_PARAMS_COUNT = 6;
+  const int RESOLVER_STATS_SUCCESSES = 0;
+  const int RESOLVER_STATS_ERRORS = 1;
+  const int RESOLVER_STATS_TIMEOUTS = 2;
+  const int RESOLVER_STATS_INTERNAL_ERRORS = 3;
+  const int RESOLVER_STATS_RTT_AVG = 4;
+  const int RESOLVER_STATS_LAST_SAMPLE_TIME = 5;
+  const int RESOLVER_STATS_USABLE = 6;
+  const int RESOLVER_STATS_COUNT = 7;
+  const int DNS_RESOLVER_LOG_VERBOSE = 0;
+  const int DNS_RESOLVER_LOG_DEBUG = 1;
+  const int DNS_RESOLVER_LOG_INFO = 2;
+  const int DNS_RESOLVER_LOG_WARNING = 3;
+  const int DNS_RESOLVER_LOG_ERROR = 4;
+  const int TC_MODE_DEFAULT = 0;
+  const int TC_MODE_UDP_TCP = 1;
+  const int TRANSPORT_UNKNOWN = -1;
+  const int TRANSPORT_CELLULAR = 0;
+  const int TRANSPORT_WIFI = 1;
+  const int TRANSPORT_BLUETOOTH = 2;
+  const int TRANSPORT_ETHERNET = 3;
+  const int TRANSPORT_VPN = 4;
+  const int TRANSPORT_WIFI_AWARE = 5;
+  const int TRANSPORT_LOWPAN = 6;
+  const int TRANSPORT_TEST = 7;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverHostsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverHostsParcel.aidl
new file mode 100644
index 0000000..c24eb61
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverHostsParcel.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable ResolverHostsParcel {
+  @utf8InCpp String ipAddr;
+  @utf8InCpp String hostName = "";
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverOptionsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverOptionsParcel.aidl
new file mode 100644
index 0000000..e806d04
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverOptionsParcel.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable ResolverOptionsParcel {
+  android.net.ResolverHostsParcel[] hosts = {};
+  int tcMode = 0;
+  boolean enforceDnsUid = false;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverParamsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverParamsParcel.aidl
new file mode 100644
index 0000000..8fec710
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/ResolverParamsParcel.aidl
@@ -0,0 +1,38 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable ResolverParamsParcel {
+  int netId;
+  int sampleValiditySeconds;
+  int successThreshold;
+  int minSamples;
+  int maxSamples;
+  int baseTimeoutMsec;
+  int retryCount;
+  @utf8InCpp String[] servers;
+  @utf8InCpp String[] domains;
+  @utf8InCpp String tlsName;
+  @utf8InCpp String[] tlsServers;
+  @utf8InCpp String[] tlsFingerprints = {};
+  @utf8InCpp String caCertificate = "";
+  int tlsConnectTimeoutMs = 0;
+  android.net.ResolverOptionsParcel resolverOptions;
+  int[] transportTypes = {};
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/DnsHealthEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/DnsHealthEventParcel.aidl
new file mode 100644
index 0000000..d32be91
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/DnsHealthEventParcel.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable DnsHealthEventParcel {
+  int netId;
+  int healthResult;
+  int[] successRttMicros;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..d8accd1
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
@@ -0,0 +1,31 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.resolv.aidl;
+/* @hide */
+interface IDnsResolverUnsolicitedEventListener {
+  oneway void onDnsHealthEvent(in android.net.resolv.aidl.DnsHealthEventParcel dnsHealthEvent);
+  oneway void onNat64PrefixEvent(in android.net.resolv.aidl.Nat64PrefixEventParcel nat64PrefixEvent);
+  oneway void onPrivateDnsValidationEvent(in android.net.resolv.aidl.PrivateDnsValidationEventParcel privateDnsValidationEvent);
+  const int DNS_HEALTH_RESULT_OK = 0;
+  const int DNS_HEALTH_RESULT_TIMEOUT = 255;
+  const int PREFIX_OPERATION_ADDED = 1;
+  const int PREFIX_OPERATION_REMOVED = 2;
+  const int VALIDATION_RESULT_SUCCESS = 1;
+  const int VALIDATION_RESULT_FAILURE = 2;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
new file mode 100644
index 0000000..2daccb0
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable Nat64PrefixEventParcel {
+  int netId;
+  int prefixOperation;
+  @utf8InCpp String prefixAddress;
+  int prefixLength;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
new file mode 100644
index 0000000..e66e21c
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/7/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable PrivateDnsValidationEventParcel {
+  int netId;
+  @utf8InCpp String ipAddress;
+  @utf8InCpp String hostname;
+  int validation;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/IDnsResolver.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/IDnsResolver.aidl
index 863927b..1f80545 100644
--- a/aidl_api/dnsresolver_aidl_interface/current/android/net/IDnsResolver.aidl
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/IDnsResolver.aidl
@@ -2,13 +2,14 @@
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
@@ -30,6 +31,7 @@
   void setLogSeverity(int logSeverity);
   void flushNetworkCache(int netId);
   void setPrefix64(int netId, @utf8InCpp String prefix);
+  void registerUnsolicitedEventListener(android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener listener);
   const int RESOLVER_PARAMS_SAMPLE_VALIDITY = 0;
   const int RESOLVER_PARAMS_SUCCESS_THRESHOLD = 1;
   const int RESOLVER_PARAMS_MIN_SAMPLES = 2;
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverHostsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverHostsParcel.aidl
index 3ab0533..c24eb61 100644
--- a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverHostsParcel.aidl
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverHostsParcel.aidl
@@ -2,13 +2,14 @@
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverOptionsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverOptionsParcel.aidl
index d55ae46..e806d04 100644
--- a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverOptionsParcel.aidl
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverOptionsParcel.aidl
@@ -2,13 +2,14 @@
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverParamsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverParamsParcel.aidl
index 5dae1ca..8fec710 100644
--- a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverParamsParcel.aidl
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverParamsParcel.aidl
@@ -2,13 +2,14 @@
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/DnsHealthEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/DnsHealthEventParcel.aidl
new file mode 100644
index 0000000..d32be91
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/DnsHealthEventParcel.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable DnsHealthEventParcel {
+  int netId;
+  int healthResult;
+  int[] successRttMicros;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..d8accd1
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
@@ -0,0 +1,31 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.resolv.aidl;
+/* @hide */
+interface IDnsResolverUnsolicitedEventListener {
+  oneway void onDnsHealthEvent(in android.net.resolv.aidl.DnsHealthEventParcel dnsHealthEvent);
+  oneway void onNat64PrefixEvent(in android.net.resolv.aidl.Nat64PrefixEventParcel nat64PrefixEvent);
+  oneway void onPrivateDnsValidationEvent(in android.net.resolv.aidl.PrivateDnsValidationEventParcel privateDnsValidationEvent);
+  const int DNS_HEALTH_RESULT_OK = 0;
+  const int DNS_HEALTH_RESULT_TIMEOUT = 255;
+  const int PREFIX_OPERATION_ADDED = 1;
+  const int PREFIX_OPERATION_REMOVED = 2;
+  const int VALIDATION_RESULT_SUCCESS = 1;
+  const int VALIDATION_RESULT_FAILURE = 2;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
new file mode 100644
index 0000000..2daccb0
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable Nat64PrefixEventParcel {
+  int netId;
+  int prefixOperation;
+  @utf8InCpp String prefixAddress;
+  int prefixLength;
+}
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
new file mode 100644
index 0000000..e66e21c
--- /dev/null
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.resolv.aidl;
+/* @hide */
+@JavaDerive(toString=true)
+parcelable PrivateDnsValidationEventParcel {
+  int netId;
+  @utf8InCpp String ipAddress;
+  @utf8InCpp String hostname;
+  int validation;
+}
diff --git a/binder/android/net/IDnsResolver.aidl b/binder/android/net/IDnsResolver.aidl
index bcf68a8..33316e3 100644
--- a/binder/android/net/IDnsResolver.aidl
+++ b/binder/android/net/IDnsResolver.aidl
@@ -18,6 +18,7 @@
 
 import android.net.ResolverParamsParcel;
 import android.net.metrics.INetdEventListener;
+import android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener;
 
 /** {@hide} */
 interface IDnsResolver {
@@ -214,4 +215,21 @@
      *         unix errno.
      */
     void setPrefix64(int netId, @utf8InCpp String prefix);
+
+    /**
+    * Register unsolicited event listener
+    * DnsResolver supports multiple unsolicited event listeners.
+    *
+    * This is a non-public interface between DnsResolver and Connectivity/NetworkStack.
+    * It is subject to change on Mainline updates without notice. DO NOT DEPEND ON IT.
+    *
+    * Only system services(Connectivity/NetworkStack) will register the unsolicited listener.
+    * Besides, there is no unregister method since the system services will be always there to
+    * listen unsolicited events.
+    *
+    * @param listener unsolicited event listener to register
+    * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+    *         unix errno.
+    */
+    void registerUnsolicitedEventListener(IDnsResolverUnsolicitedEventListener listener);
 }
diff --git a/binder/android/net/resolv/aidl/DnsHealthEventParcel.aidl b/binder/android/net/resolv/aidl/DnsHealthEventParcel.aidl
new file mode 100644
index 0000000..33f60da
--- /dev/null
+++ b/binder/android/net/resolv/aidl/DnsHealthEventParcel.aidl
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.resolv.aidl;
+
+/**
+ * The DNS health result of queries.
+ *
+ * Only sent for API calls that actually generated network traffic:
+ *  - No cache hits
+ *  - No power management errors (rcode == INTERNAL_ERROR && errno == EPERM)
+ *  - Only lookup result is OK or TIMEOUT.
+ *
+ * This must not be used for use-cases other than evaluating the health of DNS queries. The behavior
+ * of this interface will change across regular module updates, and in particular it will be updated
+ * to stop sending one event by DNS API call, and instead report network failures in realtime.
+ *
+ * Non-public API, only callable from Connectivity or NetworkStack Mainline modules.
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable DnsHealthEventParcel {
+
+    /*** The ID of the network that lookup was performed on. */
+    int netId;
+
+    /**
+     * The health result of queries.
+     * Currently, this value is for data stall detection and it only checks whether result is
+     * timeout or not. So if the result is neither OK nor TIMEOUT, will not send this event.
+     */
+    int healthResult;
+
+    /*** The round trip time for each dns query that received a reply */
+    int[] successRttMicros;
+}
diff --git a/binder/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl b/binder/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..b0d6553
--- /dev/null
+++ b/binder/android/net/resolv/aidl/IDnsResolverUnsolicitedEventListener.aidl
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.resolv.aidl;
+
+import android.net.resolv.aidl.DnsHealthEventParcel;
+import android.net.resolv.aidl.Nat64PrefixEventParcel;
+import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
+
+/**
+ * Unsolicited dns resolver events listener.
+ * This one-way interface groups asynchronous notifications sent by dns resolver to any process that
+ * registered itself via IDnsResolver.registerUnsolicitedEventListener.
+ *
+ * {@hide}
+ */
+oneway interface IDnsResolverUnsolicitedEventListener {
+
+    /*** Types for {@code healthResult} of {@code DnsHealthEventParcel}. */
+    const int DNS_HEALTH_RESULT_OK = 0;
+    const int DNS_HEALTH_RESULT_TIMEOUT = 255;
+
+    /**
+     * Represents a DNS health result of queries.
+     *
+     * Sent by the resolver when it has events to report about its observed network health.
+     * Refer to DnsHealthEventParcel for more details.
+     *
+     * @param dnsHealthEvent the DNS health result.
+     */
+    void onDnsHealthEvent(in DnsHealthEventParcel dnsHealthEvent);
+
+    /*** Types for {@code prefixOperation} of {@code Nat64PrefixEventParcel}. */
+    const int PREFIX_OPERATION_ADDED = 1;
+    const int PREFIX_OPERATION_REMOVED = 2;
+
+    /**
+     * Represents a NAT64 prefix operation.
+     *
+     * @param nat64PrefixEvent the NAT64 prefix status.
+     */
+    void onNat64PrefixEvent(in Nat64PrefixEventParcel nat64PrefixEvent);
+
+    /*** Types for {@code validation} of {@code PrivateDnsValidationEventParcel}. */
+    const int VALIDATION_RESULT_SUCCESS = 1;
+    const int VALIDATION_RESULT_FAILURE = 2;
+
+    /**
+     * Represents a private DNS validation result.
+     *
+     * @param privateDnsValidationEvent the private DNS validation result.
+     */
+    void onPrivateDnsValidationEvent(in PrivateDnsValidationEventParcel privateDnsValidationEvent);
+}
diff --git a/binder/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl b/binder/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
new file mode 100644
index 0000000..45e25a1
--- /dev/null
+++ b/binder/android/net/resolv/aidl/Nat64PrefixEventParcel.aidl
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.resolv.aidl;
+
+/**
+ * Nat64 prefix operation event.
+ *
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable Nat64PrefixEventParcel {
+
+    /** The ID of the network the prefix operation was performed on. */
+    int netId;
+
+    /**
+     * The NAT64 prefix operation.
+     * There is only one prefix at a time for each netId. If a prefix is added, it replaces the
+     * previous-added prefix.
+     */
+    int prefixOperation;
+
+    /** The detected NAT64 prefix address. */
+    @utf8InCpp String prefixAddress;
+
+    /** The prefix length associated with this NAT64 prefix. */
+    int prefixLength;
+}
diff --git a/binder/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl b/binder/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
new file mode 100644
index 0000000..e5d7f78
--- /dev/null
+++ b/binder/android/net/resolv/aidl/PrivateDnsValidationEventParcel.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.resolv.aidl;
+
+/**
+ * A private DNS validation result.
+ *
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable PrivateDnsValidationEventParcel {
+
+    /** The ID of the network the validation was performed on. */
+    int netId;
+
+    /** The IP address for which validation was performed. */
+    @utf8InCpp String ipAddress;
+
+    /** The hostname for which validation was performed. */
+    @utf8InCpp String hostname;
+
+    /** The validation result. */
+    int validation;
+}
diff --git a/tests/Android.bp b/tests/Android.bp
index 632aeb8..b832348 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -165,7 +165,6 @@
     ],
     static_libs: [
         "dnsresolver_aidl_interface-unstable-ndk_platform",
-        "libbpf_android",
         "libcrypto_static",
         "libgmock",
         "libnetd_test_dnsresponder_ndk",
@@ -177,6 +176,7 @@
         "netd_aidl_interface-ndk_platform",
         "netd_event_listener_interface-unstable-ndk_platform",
         "libipchecksum",
+        "resolv_unsolicited_listener",
     ],
     // This test talks to the DnsResolver module over a binary protocol on a socket, so keep it as
     // multilib setting is worth because we might be able to get some coverage for the case where
diff --git a/tests/dnsresolver_binder_test.cpp b/tests/dnsresolver_binder_test.cpp
index 50f446e..18bd6dd 100644
--- a/tests/dnsresolver_binder_test.cpp
+++ b/tests/dnsresolver_binder_test.cpp
@@ -40,6 +40,7 @@
 
 #include "dns_metrics_listener/base_metrics_listener.h"
 #include "dns_metrics_listener/test_metrics.h"
+#include "unsolicited_listener/unsolicited_event_listener.h"
 
 #include "ResolverStats.h"
 #include "dns_responder.h"
@@ -55,6 +56,7 @@
 using android::base::unique_fd;
 using android::net::ResolverStats;
 using android::net::metrics::TestOnDnsEvent;
+using android::net::resolv::aidl::UnsolicitedEventListener;
 using android::netdutils::Stopwatch;
 
 // TODO: make this dynamic and stop depending on implementation details.
@@ -121,19 +123,33 @@
         // Basic regexp to match dump output lines. Matches the beginning and end of the line, and
         // puts the output of the command itself into the first match group.
         // Example: "      11-05 00:23:39.481 myCommand(args) <2.02ms>".
+        // Note: There are 4 leading blank characters in Q, but 6 in R.
         const std::basic_regex lineRegex(
-                "^      [0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[.][0-9]{3} "
+                "^ {4,6}[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[.][0-9]{3} "
                 "(.*)"
                 " <[0-9]+[.][0-9]{2}ms>$");
 
         // For each element of testdata, check that the expected output appears in the dump output.
         // If not, fail the test and use hintRegex to print similar lines to assist in debugging.
         for (const auto& td : mExpectedLogData) {
+            const std::string toErase = "(null)";
             const bool found =
                     std::any_of(lines.begin(), lines.end(), [&](const std::string& line) {
                         std::smatch match;
                         if (!std::regex_match(line, match, lineRegex)) return false;
-                        return (match.size() == 2) && (match[1].str() == td.output);
+                        if (match.size() != 2) return false;
+
+                        // The binder_to_string format is changed from S that will add "(null)" to
+                        // the log on method's argument if binder object is null. But Q and R don't
+                        // have this format in log. So to make register null listener tests are
+                        // compatible from all version, just remove the "(null)" argument from
+                        // output logs if existed.
+                        std::string output = match[1].str();
+                        const auto pos = output.find(toErase);
+                        if (pos != std::string::npos && pos < output.length()) {
+                            output.erase(pos, toErase.length());
+                        }
+                        return output == td.output;
                     });
             EXPECT_TRUE(found) << "Didn't find line '" << td.output << "' in dumpsys output.";
             if (found) continue;
@@ -272,16 +288,16 @@
 }
 
 TEST_F(DnsResolverBinderTest, RegisterEventListener_DuplicateSubscription) {
-    class DummyListener : public android::net::metrics::BaseMetricsListener {};
+    class FakeListener : public android::net::metrics::BaseMetricsListener {};
 
     // Expect to subscribe successfully.
-    std::shared_ptr<DummyListener> dummyListener = ndk::SharedRefBase::make<DummyListener>();
-    ::ndk::ScopedAStatus status = mDnsResolver->registerEventListener(dummyListener);
+    std::shared_ptr<FakeListener> fakeListener = ndk::SharedRefBase::make<FakeListener>();
+    ::ndk::ScopedAStatus status = mDnsResolver->registerEventListener(fakeListener);
     ASSERT_TRUE(status.isOk()) << status.getMessage();
     mExpectedLogData.push_back({"registerEventListener()", "registerEventListener.*"});
 
     // Expect to subscribe failed with registered listener instance.
-    status = mDnsResolver->registerEventListener(dummyListener);
+    status = mDnsResolver->registerEventListener(fakeListener);
     ASSERT_FALSE(status.isOk());
     ASSERT_EQ(EEXIST, status.getServiceSpecificError());
     mExpectedLogData.push_back(
@@ -289,6 +305,34 @@
              "registerEventListener.*17"});
 }
 
+TEST_F(DnsResolverBinderTest, RegisterUnsolicitedEventListener_NullListener) {
+    ::ndk::ScopedAStatus status = mDnsResolver->registerUnsolicitedEventListener(nullptr);
+    ASSERT_FALSE(status.isOk());
+    ASSERT_EQ(EINVAL, status.getServiceSpecificError());
+    mExpectedLogData.push_back(
+            {"registerUnsolicitedEventListener() -> ServiceSpecificException(22, \"Invalid "
+             "argument\")",
+             "registerUnsolicitedEventListener.*22"});
+}
+
+TEST_F(DnsResolverBinderTest, RegisterUnsolicitedEventListener_DuplicateSubscription) {
+    // Expect to subscribe successfully.
+    std::shared_ptr<UnsolicitedEventListener> listener =
+            ndk::SharedRefBase::make<UnsolicitedEventListener>(TEST_NETID);
+    ::ndk::ScopedAStatus status = mDnsResolver->registerUnsolicitedEventListener(listener);
+    ASSERT_TRUE(status.isOk()) << status.getMessage();
+    mExpectedLogData.push_back(
+            {"registerUnsolicitedEventListener()", "registerUnsolicitedEventListener.*"});
+
+    // Expect to subscribe failed with registered listener instance.
+    status = mDnsResolver->registerUnsolicitedEventListener(listener);
+    ASSERT_FALSE(status.isOk());
+    ASSERT_EQ(EEXIST, status.getServiceSpecificError());
+    mExpectedLogData.push_back(
+            {"registerUnsolicitedEventListener() -> ServiceSpecificException(17, \"File exists\")",
+             "registerUnsolicitedEventListener.*17"});
+}
+
 // TODO: Move this test to resolv_integration_test.cpp
 TEST_F(DnsResolverBinderTest, RegisterEventListener_onDnsEvent) {
     // The test configs are used to trigger expected events. The expected results are defined in
diff --git a/tests/resolv_gold_test.cpp b/tests/resolv_gold_test.cpp
index f7f417b..c5b24aa 100644
--- a/tests/resolv_gold_test.cpp
+++ b/tests/resolv_gold_test.cpp
@@ -369,6 +369,7 @@
     ASSERT_NO_FATAL_FAILURE(SetResolversWithTls());
     EXPECT_TRUE(WaitForPrivateDnsValidation(tls.listen_address()));
     tls.setDelayQueries(2);
+    tls.setDelayQueriesTimeout(200);
 
     dns.clearQueries();
     addrinfo* res = nullptr;
@@ -431,6 +432,7 @@
     ASSERT_TRUE(dns.startServer());
     test::DnsTlsFrontend tls;
     tls.setDelayQueries(2);
+    tls.setDelayQueriesTimeout(200);
 
     if (protocol == DnsProtocol::CLEARTEXT) {
         ASSERT_NO_FATAL_FAILURE(SetResolvers());
diff --git a/tests/resolv_integration_test.cpp b/tests/resolv_integration_test.cpp
index 09c71bf..cef8b6b 100644
--- a/tests/resolv_integration_test.cpp
+++ b/tests/resolv_integration_test.cpp
@@ -27,7 +27,6 @@
 #include <arpa/inet.h>
 #include <arpa/nameser.h>
 #include <binder/ProcessState.h>
-#include <bpf/BpfUtils.h>
 #include <cutils/sockets.h>
 #include <gmock/gmock-matchers.h>
 #include <gtest/gtest.h>
@@ -72,6 +71,7 @@
 #include "tests/dns_responder/dns_tls_frontend.h"
 #include "tests/resolv_test_utils.h"
 #include "tests/tun_forwarder.h"
+#include "tests/unsolicited_listener/unsolicited_event_listener.h"
 
 // Valid VPN netId range is 100 ~ 65535
 constexpr int TEST_VPN_NETID = 65502;
@@ -95,6 +95,10 @@
 using aidl::android::net::INetd;
 using aidl::android::net::ResolverParamsParcel;
 using aidl::android::net::metrics::INetdEventListener;
+using aidl::android::net::resolv::aidl::DnsHealthEventParcel;
+using aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener;
+using aidl::android::net::resolv::aidl::Nat64PrefixEventParcel;
+using aidl::android::net::resolv::aidl::PrivateDnsValidationEventParcel;
 using android::base::Error;
 using android::base::ParseInt;
 using android::base::Result;
@@ -103,6 +107,7 @@
 using android::net::ResolverStats;
 using android::net::TunForwarder;
 using android::net::metrics::DnsMetricsListener;
+using android::net::resolv::aidl::UnsolicitedEventListener;
 using android::netdutils::enableSockopt;
 using android::netdutils::makeSlice;
 using android::netdutils::ResponseCode;
@@ -205,6 +210,12 @@
                 TEST_NETID /*monitor specific network*/);
         ASSERT_TRUE(resolvService->registerEventListener(sDnsMetricsListener).isOk());
 
+        // Subscribe the unsolicited event listener for verifying unsolicited event contents.
+        sUnsolicitedEventListener = ndk::SharedRefBase::make<UnsolicitedEventListener>(
+                TEST_NETID /*monitor specific network*/);
+        ASSERT_TRUE(
+                resolvService->registerUnsolicitedEventListener(sUnsolicitedEventListener).isOk());
+
         // Start the binder thread pool for listening DNS metrics events and receiving death
         // recipient.
         ABinderProcess_startThreadPool();
@@ -215,6 +226,7 @@
     void SetUp() {
         mDnsClient.SetUp();
         sDnsMetricsListener->reset();
+        sUnsolicitedEventListener->reset();
     }
 
     void TearDown() {
@@ -248,15 +260,25 @@
 
     bool WaitForNat64Prefix(ExpectNat64PrefixStatus status,
                             std::chrono::milliseconds timeout = std::chrono::milliseconds(1000)) {
-        return sDnsMetricsListener->waitForNat64Prefix(status, timeout);
+        return sDnsMetricsListener->waitForNat64Prefix(status, timeout) &&
+               sUnsolicitedEventListener->waitForNat64Prefix(
+                       status == EXPECT_FOUND
+                               ? IDnsResolverUnsolicitedEventListener::PREFIX_OPERATION_ADDED
+                               : IDnsResolverUnsolicitedEventListener::PREFIX_OPERATION_REMOVED,
+                       timeout);
     }
 
     bool WaitForPrivateDnsValidation(std::string serverAddr, bool validated) {
-        return sDnsMetricsListener->waitForPrivateDnsValidation(serverAddr, validated);
+        return sDnsMetricsListener->waitForPrivateDnsValidation(serverAddr, validated) &&
+               sUnsolicitedEventListener->waitForPrivateDnsValidation(
+                       serverAddr,
+                       validated ? IDnsResolverUnsolicitedEventListener::VALIDATION_RESULT_SUCCESS
+                                 : IDnsResolverUnsolicitedEventListener::VALIDATION_RESULT_FAILURE);
     }
 
     bool hasUncaughtPrivateDnsValidation(const std::string& serverAddr) {
-        return sDnsMetricsListener->findValidationRecord(serverAddr);
+        return sDnsMetricsListener->findValidationRecord(serverAddr) &&
+               sUnsolicitedEventListener->findValidationRecord(serverAddr);
     }
 
     void ExpectDnsEvent(int32_t eventType, int32_t returnCode, const std::string& hostname,
@@ -271,6 +293,20 @@
             if (dnsEvent.value() == expect) break;
             LOG(INFO) << "Skip unexpected DnsEvent: " << dnsEvent.value();
         } while (true);
+
+        while (returnCode == 0 || returnCode == RCODE_TIMEOUT) {
+            // Blocking call until timeout.
+            Result<int> result = sUnsolicitedEventListener->popDnsHealthResult();
+            ASSERT_TRUE(result.ok()) << "Expected dns health result is " << returnCode;
+            if ((returnCode == 0 &&
+                 result.value() == IDnsResolverUnsolicitedEventListener::DNS_HEALTH_RESULT_OK) ||
+                (returnCode == RCODE_TIMEOUT &&
+                 result.value() ==
+                         IDnsResolverUnsolicitedEventListener::DNS_HEALTH_RESULT_TIMEOUT)) {
+                break;
+            }
+            LOG(INFO) << "Skip unexpected dns health result:" << result.value();
+        }
     }
 
     enum class StatsCmp { LE, EQ };
@@ -368,6 +404,9 @@
     static std::shared_ptr<DnsMetricsListener>
             sDnsMetricsListener;  // Initialized in SetUpTestSuite.
 
+    inline static std::shared_ptr<UnsolicitedEventListener>
+            sUnsolicitedEventListener;  // Initialized in SetUpTestSuite.
+
     // Use a shared static death recipient to monitor the service death. The static death
     // recipient could monitor the death not only during the test but also between tests.
     static AIBinder_DeathRecipient* sResolvDeathRecipient;  // Initialized in SetUpTestSuite.
@@ -4013,6 +4052,7 @@
     EXPECT_TRUE(WaitForNat64Prefix(EXPECT_NOT_FOUND));
 
     EXPECT_EQ(0, sDnsMetricsListener->getUnexpectedNat64PrefixUpdates());
+    EXPECT_EQ(0, sUnsolicitedEventListener->getUnexpectedNat64PrefixUpdates());
 }
 
 namespace {
@@ -4161,10 +4201,6 @@
 }
 
 TEST_F(ResolverTest, BlockDnsQueryWithUidRule) {
-    // This test relies on blocking traffic on loopback, which xt_qtaguid does not do.
-    // See aosp/358413 and b/34444781 for why.
-    SKIP_IF_BPF_NOT_SUPPORTED;
-
     constexpr char listen_addr1[] = "127.0.0.4";
     constexpr char listen_addr2[] = "::1";
     constexpr char host_name[] = "howdy.example.com.";
@@ -4212,8 +4248,6 @@
 }
 
 TEST_F(ResolverTest, EnforceDnsUid) {
-    SKIP_IF_BPF_NOT_SUPPORTED;
-
     constexpr char listen_addr1[] = "127.0.0.4";
     constexpr char listen_addr2[] = "::1";
     constexpr char host_name[] = "howdy.example.com.";
@@ -4292,9 +4326,6 @@
             {hostname2, ns_type::ns_t_a, "1.2.3.5"},
     };
 
-    // The resolver will adjust the timeout value to 1000ms since the value is too small.
-    ScopedSystemProperties scopedSystemProperties(kDotConnectTimeoutMsFlag, "100");
-
     static const struct TestConfig {
         bool asyncHandshake;
         int maxRetries;
@@ -4323,6 +4354,8 @@
         test::DnsTlsFrontend tls(addr, "853", addr, "53");
         ASSERT_TRUE(tls.startServer());
 
+        // The resolver will adjust the timeout value to 1000ms since the value is too small.
+        ScopedSystemProperties scopedSystemProperties(kDotConnectTimeoutMsFlag, "100");
         ScopedSystemProperties scopedSystemProperties1(kDotAsyncHandshakeFlag,
                                                        config.asyncHandshake ? "1" : "0");
         ScopedSystemProperties scopedSystemProperties2(kDotMaxretriesFlag,
@@ -5370,10 +5403,6 @@
 }
 
 TEST_F(ResolverTest, BlockDnsQueryUidDoesNotLeadToBadServer) {
-    // This test relies on blocking traffic on loopback, which xt_qtaguid does not do.
-    // See aosp/358413 and b/34444781 for why.
-    SKIP_IF_BPF_NOT_SUPPORTED;
-
     constexpr char listen_addr1[] = "127.0.0.4";
     constexpr char listen_addr2[] = "::1";
     test::DNSResponder dns1(listen_addr1);
@@ -5423,8 +5452,6 @@
     StartDns(dns2, {{kHelloExampleCom, ns_type::ns_t_a, kHelloExampleComAddrV4}});
     StartDns(dns3, {{kHelloExampleCom, ns_type::ns_t_a, kHelloExampleComAddrV4}});
 
-    ScopedSystemProperties scopedSystemProperties(kSortNameserversFlag, "1");
-
     // NOTE: the servers must be sorted alphabetically.
     std::vector<std::string> serverList = {
             dns1.listen_address(),
@@ -5437,6 +5464,9 @@
         const int queryNum = 50;
         int64_t accumulatedTime = 0;
 
+        // The flag can be reset any time. It's better to re-setup the flag in each iteration.
+        ScopedSystemProperties scopedSystemProperties(kSortNameserversFlag, "1");
+
         // Restart the testing network to 1) make the flag take effect and 2) reset the statistics.
         resetNetwork();
 
@@ -6003,7 +6033,6 @@
 
 TEST_F(ResolverMultinetworkTest, DnsWithVpn) {
     SKIP_IF_REMOTE_VERSION_LESS_THAN(mDnsClient.resolvService(), 4);
-    SKIP_IF_BPF_NOT_SUPPORTED;
     constexpr char host_name[] = "ohayou.example.com.";
     constexpr char ipv4_addr[] = "192.0.2.0";
     constexpr char ipv6_addr[] = "2001:db8:cafe:d00d::31";
diff --git a/tests/unsolicited_listener/Android.bp b/tests/unsolicited_listener/Android.bp
new file mode 100644
index 0000000..155bc1d
--- /dev/null
+++ b/tests/unsolicited_listener/Android.bp
@@ -0,0 +1,14 @@
+cc_test_library {
+    name: "resolv_unsolicited_listener",
+    defaults: ["resolv_test_defaults"],
+    srcs: [
+        "unsolicited_event_listener.cpp",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+    ],
+    static_libs: [
+        "dnsresolver_aidl_interface-unstable-ndk_platform",
+        "libutils",
+    ],
+}
diff --git a/tests/unsolicited_listener/unsolicited_event_listener.cpp b/tests/unsolicited_listener/unsolicited_event_listener.cpp
new file mode 100644
index 0000000..773c385
--- /dev/null
+++ b/tests/unsolicited_listener/unsolicited_event_listener.cpp
@@ -0,0 +1,130 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "unsolicited_event_listener.h"
+
+#include <thread>
+
+#include <android-base/chrono_utils.h>
+#include <android-base/format.h>
+
+namespace android::net::resolv::aidl {
+
+using ::aidl::android::net::resolv::aidl::DnsHealthEventParcel;
+using ::aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener;
+using ::aidl::android::net::resolv::aidl::Nat64PrefixEventParcel;
+using ::aidl::android::net::resolv::aidl::PrivateDnsValidationEventParcel;
+using android::base::Error;
+using android::base::Result;
+using android::base::ScopedLockAssertion;
+using std::chrono::milliseconds;
+
+constexpr milliseconds kEventTimeoutMs{5000};
+constexpr milliseconds kRetryIntervalMs{20};
+
+::ndk::ScopedAStatus UnsolicitedEventListener::onDnsHealthEvent(const DnsHealthEventParcel& event) {
+    std::lock_guard lock(mMutex);
+    if (event.netId == mNetId) mDnsHealthResultRecords.push(event.healthResult);
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus UnsolicitedEventListener::onNat64PrefixEvent(
+        const Nat64PrefixEventParcel& event) {
+    std::lock_guard lock(mMutex);
+    mUnexpectedNat64PrefixUpdates++;
+    if (event.netId == mNetId) {
+        mNat64PrefixAddress = (event.prefixOperation ==
+                               IDnsResolverUnsolicitedEventListener::PREFIX_OPERATION_ADDED)
+                                      ? event.prefixAddress
+                                      : "";
+    }
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus UnsolicitedEventListener::onPrivateDnsValidationEvent(
+        const PrivateDnsValidationEventParcel& event) {
+    {
+        std::lock_guard lock(mMutex);
+        // keep updating the server to have latest validation status.
+        mValidationRecords.insert_or_assign({event.netId, event.ipAddress}, event.validation);
+    }
+    mCv.notify_one();
+    return ::ndk::ScopedAStatus::ok();
+}
+
+bool UnsolicitedEventListener::waitForPrivateDnsValidation(const std::string& serverAddr,
+                                                           int validation) {
+    const auto now = std::chrono::steady_clock::now();
+
+    std::unique_lock lock(mMutex);
+    ScopedLockAssertion assume_lock(mMutex);
+
+    // onPrivateDnsValidationEvent() might already be invoked. Search for the record first.
+    do {
+        if (findAndRemoveValidationRecord({mNetId, serverAddr}, validation)) return true;
+    } while (mCv.wait_until(lock, now + kEventTimeoutMs) != std::cv_status::timeout);
+
+    // Timeout.
+    return false;
+}
+
+bool UnsolicitedEventListener::findAndRemoveValidationRecord(const ServerKey& key, int value) {
+    auto it = mValidationRecords.find(key);
+    if (it != mValidationRecords.end() && it->second == value) {
+        mValidationRecords.erase(it);
+        return true;
+    }
+    return false;
+}
+
+bool UnsolicitedEventListener::waitForNat64Prefix(int operation, const milliseconds& timeout) {
+    android::base::Timer t;
+    while (t.duration() < timeout) {
+        {
+            std::lock_guard lock(mMutex);
+            if ((operation == IDnsResolverUnsolicitedEventListener::PREFIX_OPERATION_ADDED &&
+                 !mNat64PrefixAddress.empty()) ||
+                (operation == IDnsResolverUnsolicitedEventListener::PREFIX_OPERATION_REMOVED &&
+                 mNat64PrefixAddress.empty())) {
+                mUnexpectedNat64PrefixUpdates--;
+                return true;
+            }
+        }
+        std::this_thread::sleep_for(kRetryIntervalMs);
+    }
+    return false;
+}
+
+Result<int> UnsolicitedEventListener::popDnsHealthResult() {
+    // Wait until the queue is not empty or timeout.
+    android::base::Timer t;
+    while (t.duration() < milliseconds{1000}) {
+        {
+            std::lock_guard lock(mMutex);
+            if (!mDnsHealthResultRecords.empty()) break;
+        }
+        std::this_thread::sleep_for(kRetryIntervalMs);
+    }
+
+    std::lock_guard lock(mMutex);
+    if (mDnsHealthResultRecords.empty()) return Error() << "Dns health result record is empty";
+
+    auto ret = mDnsHealthResultRecords.front();
+    mDnsHealthResultRecords.pop();
+    return ret;
+}
+
+}  // namespace android::net::resolv::aidl
diff --git a/tests/unsolicited_listener/unsolicited_event_listener.h b/tests/unsolicited_listener/unsolicited_event_listener.h
new file mode 100644
index 0000000..5edabd4
--- /dev/null
+++ b/tests/unsolicited_listener/unsolicited_event_listener.h
@@ -0,0 +1,106 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <condition_variable>
+#include <map>
+#include <queue>
+#include <string>
+#include <utility>
+
+#include <aidl/android/net/resolv/aidl/BnDnsResolverUnsolicitedEventListener.h>
+#include <android-base/result.h>
+#include <android-base/thread_annotations.h>
+
+namespace android::net::resolv::aidl {
+
+class UnsolicitedEventListener
+    : public ::aidl::android::net::resolv::aidl::BnDnsResolverUnsolicitedEventListener {
+  public:
+    UnsolicitedEventListener() = delete;
+    UnsolicitedEventListener(int32_t netId) : mNetId(netId){};
+    ~UnsolicitedEventListener() = default;
+
+    ::ndk::ScopedAStatus onDnsHealthEvent(
+            const ::aidl::android::net::resolv::aidl::DnsHealthEventParcel&) override;
+    ::ndk::ScopedAStatus onNat64PrefixEvent(
+            const ::aidl::android::net::resolv::aidl::Nat64PrefixEventParcel&) override;
+    ::ndk::ScopedAStatus onPrivateDnsValidationEvent(
+            const ::aidl::android::net::resolv::aidl::PrivateDnsValidationEventParcel&) override;
+
+    // Wait for the expected private DNS validation result until timeout.
+    bool waitForPrivateDnsValidation(const std::string& serverAddr, int validation);
+
+    // Wait for expected NAT64 prefix operation until timeout.
+    bool waitForNat64Prefix(int operation, const std::chrono::milliseconds& timeout)
+            EXCLUDES(mMutex);
+
+    // Pop up last receiving dns health result.
+    android::base::Result<int> popDnsHealthResult() EXCLUDES(mMutex);
+
+    // Return true if a validation result for |serverAddr| is found; otherwise, return false.
+    bool findValidationRecord(const std::string& serverAddr) const EXCLUDES(mMutex) {
+        std::lock_guard lock(mMutex);
+        return mValidationRecords.find({mNetId, serverAddr}) != mValidationRecords.end();
+    }
+
+    // Returns the number of updates to the NAT64 prefix that have not yet been waited for.
+    int getUnexpectedNat64PrefixUpdates() const EXCLUDES(mMutex) {
+        std::lock_guard lock(mMutex);
+        return mUnexpectedNat64PrefixUpdates;
+    }
+
+    void reset() EXCLUDES(mMutex) {
+        std::lock_guard lock(mMutex);
+        mValidationRecords.clear();
+        mUnexpectedNat64PrefixUpdates = 0;
+
+        std::queue<int> emptyQueue;
+        std::swap(mDnsHealthResultRecords, emptyQueue);
+    }
+
+  private:
+    typedef std::pair<int32_t, std::string> ServerKey;
+
+    // Search mValidationRecords. Return true if |key| exists and its value is equal to
+    // |value|, and then remove it; otherwise, return false.
+    bool findAndRemoveValidationRecord(const ServerKey& key, int value) REQUIRES(mMutex);
+
+    // Monitor the event which was fired on specific network id.
+    const int32_t mNetId;
+
+    // Used to store the data from onPrivateDnsValidationEvent.
+    std::map<ServerKey, int> mValidationRecords GUARDED_BY(mMutex);
+
+    // The NAT64 prefix address of the network |mNetId|. It is updated by onNat64PrefixEvent().
+    std::string mNat64PrefixAddress GUARDED_BY(mMutex);
+
+    // The number of updates to the NAT64 prefix of network |mNetId| that have not yet been waited
+    // for. Increases by 1 every time onNat64PrefixEvent is called, and decreases by 1 every time
+    // waitForNat64Prefix returns true.
+    // This allows tests to check that no unexpected events have been received without having to
+    // resort to timeouts that make the tests slower and flakier.
+    int mUnexpectedNat64PrefixUpdates GUARDED_BY(mMutex);
+
+    // Used to store the dns health result from onDnsHealthEvent().
+    std::queue<int> mDnsHealthResultRecords GUARDED_BY(mMutex);
+
+    mutable std::mutex mMutex;
+    std::condition_variable mCv;
+};
+
+}  // namespace android::net::resolv::aidl
diff --git a/util.cpp b/util.cpp
index b8df74c..458f3c6 100644
--- a/util.cpp
+++ b/util.cpp
@@ -17,6 +17,7 @@
 
 #include "util.h"
 
+#include <android-base/format.h>
 #include <android-base/parseint.h>
 #include <server_configurable_flags/get_flags.h>
 
@@ -45,3 +46,13 @@
     ParseInt(GetServerConfigurableFlag("netd_native", flagName, ""), &val);
     return val;
 }
+
+std::string timestampToString(const std::chrono::system_clock::time_point& ts) {
+    using std::chrono::duration_cast;
+    using std::chrono::milliseconds;
+    const auto time_sec = std::chrono::system_clock::to_time_t(ts);
+    char buf[32];
+    std::strftime(buf, sizeof(buf), "%H:%M:%S", std::localtime(&time_sec));
+    int ms = duration_cast<milliseconds>(ts.time_since_epoch()).count() % 1000;
+    return fmt::format("{}.{:03d}", buf, ms);
+}
diff --git a/util.h b/util.h
index 38f09b8..bb4a598 100644
--- a/util.h
+++ b/util.h
@@ -17,6 +17,7 @@
 
 #pragma once
 
+#include <chrono>
 #include <string>
 
 #include <netinet/in.h>
@@ -30,6 +31,9 @@
 // TODO: Migrate it to DnsResolverExperiments.cpp
 int getExperimentFlagInt(const std::string& flagName, int defaultValue);
 
+// Convert time_point to readable string format "hr:min:sec.ms".
+std::string timestampToString(const std::chrono::system_clock::time_point& ts);
+
 // When sdk X release branch is created, aosp's sdk version would still be X-1,
 // internal would be X. Also there might be some different setting between real devices and
 // CF. Below is the example for the sdk related properties in later R development stage. (internal
@@ -47,3 +51,8 @@
             android::base::GetUintProperty<uint64_t>("ro.product.first_api_level", 0);
     return std::max(buildVersionSdk + !!buildVersionPreviewSdk, firstApiLevel);
 }
+
+// It's the identical strategy as frameworks/base/core/java/android/os/Build.java did.
+inline bool isUserDebugBuild() {
+    return (android::base::GetProperty("ro.build.type", "user") == "userdebug");
+}