Implement MULTICAST_DNS source for new HostResolver API.

Only used when explicitly requested. The MDnsClient is created and
starts listening for MDNS messages at first use in HostResolverImpl.

For now supports only a basic feature set, eg we'll DCHECK if canonname
is requested (in contrast to the async resolver behavior that was
unnoticed until recently due to the lack of a DCHECK).

Bug: 846423
Cq-Include-Trybots: luci.chromium.try:linux_mojo
Change-Id: Ib296ac432d392baee2454587693b2537513f461c
Reviewed-on: https://chromium-review.googlesource.com/1211542
Reviewed-by: Matt Menke <[email protected]>
Reviewed-by: Tom Sepez <[email protected]>
Commit-Queue: Eric Orth <[email protected]>
Cr-Commit-Position: refs/heads/master@{#595956}
diff --git a/net/dns/host_resolver_impl.cc b/net/dns/host_resolver_impl.cc
index 3d29b9d2..cdad914 100644
--- a/net/dns/host_resolver_impl.cc
+++ b/net/dns/host_resolver_impl.cc
@@ -64,7 +64,9 @@
 #include "net/dns/dns_response.h"
 #include "net/dns/dns_transaction.h"
 #include "net/dns/dns_util.h"
+#include "net/dns/host_resolver_mdns_task.h"
 #include "net/dns/host_resolver_proc.h"
+#include "net/dns/mdns_client.h"
 #include "net/log/net_log.h"
 #include "net/log/net_log_capture_mode.h"
 #include "net/log/net_log_event_type.h"
@@ -76,6 +78,10 @@
 #include "net/socket/datagram_client_socket.h"
 #include "url/url_canon_ip.h"
 
+#if BUILDFLAG(ENABLE_MDNS)
+#include "net/dns/mdns_client_impl.h"
+#endif
+
 #if defined(OS_WIN)
 #include "net/base/winsock_init.h"
 #endif
@@ -1512,7 +1518,7 @@
       // This will destroy the Job.
       CompleteRequests(
           MakeCacheEntry(OK, addr_list, HostCache::Entry::SOURCE_HOSTS),
-          base::TimeDelta());
+          base::TimeDelta(), true /* allow_cache */);
       return true;
     }
     return false;
@@ -1525,7 +1531,7 @@
   }
 
   bool is_running() const {
-    return is_dns_running() || is_proc_running();
+    return is_dns_running() || is_mdns_running() || is_proc_running();
   }
 
  private:
@@ -1617,7 +1623,8 @@
     switch (key_.host_resolver_source) {
       case HostResolverSource::ANY:
         if (resolver_->HaveDnsConfig() &&
-            !ResemblesMulticastDNSName(key_.hostname)) {
+            !ResemblesMulticastDNSName(key_.hostname) &&
+            !(key_.host_resolver_flags & HOST_RESOLVER_CANONNAME)) {
           StartDnsTask();
         } else {
           StartProcTask();
@@ -1633,6 +1640,9 @@
 
         StartDnsTask();
         break;
+      case HostResolverSource::MULTICAST_DNS:
+        StartMdnsTask();
+        break;
     }
 
     // Caution: Job::Start must not complete synchronously.
@@ -1643,7 +1653,7 @@
   // TaskScheduler threads low, we will need to use an "inner"
   // PrioritizedDispatcher with tighter limits.
   void StartProcTask() {
-    DCHECK(!is_dns_running());
+    DCHECK(!is_running());
     proc_task_ = std::make_unique<ProcTask>(
         key_, resolver_->proc_params_,
         base::BindOnce(&Job::OnProcTaskComplete, base::Unretained(this),
@@ -1693,7 +1703,7 @@
     // Don't store the |ttl| in cache since it's not obtained from the server.
     CompleteRequests(
         MakeCacheEntry(net_error, addr_list, HostCache::Entry::SOURCE_UNKNOWN),
-        ttl);
+        ttl, true /* allow_cache */);
   }
 
   void StartDnsTask() {
@@ -1751,7 +1761,7 @@
       CompleteRequests(
           HostCache::Entry(net_error, AddressList(),
                            HostCache::Entry::Source::SOURCE_UNKNOWN, ttl),
-          ttl);
+          ttl, true /* allow_cache */);
     }
   }
 
@@ -1784,7 +1794,7 @@
     } else {
       CompleteRequests(MakeCacheEntryWithTTL(net_error, addr_list,
                                              HostCache::Entry::SOURCE_DNS, ttl),
-                       bounded_ttl);
+                       bounded_ttl, true /* allow_cache */);
     }
   }
 
@@ -1801,6 +1811,50 @@
       dns_task_->StartSecondTransaction();
   }
 
+  void StartMdnsTask() {
+    DCHECK(!is_running());
+
+    // No flags are supported for MDNS except
+    // HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6 (which is not actually an
+    // input flag).
+    DCHECK_EQ(0, key_.host_resolver_flags &
+                     ~HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6);
+
+    std::vector<HostResolver::DnsQueryType> query_types;
+    switch (key_.address_family) {
+      case ADDRESS_FAMILY_UNSPECIFIED:
+        query_types.push_back(HostResolver::DnsQueryType::A);
+        query_types.push_back(HostResolver::DnsQueryType::AAAA);
+        break;
+      case ADDRESS_FAMILY_IPV4:
+        query_types.push_back(HostResolver::DnsQueryType::A);
+        break;
+      case ADDRESS_FAMILY_IPV6:
+        query_types.push_back(HostResolver::DnsQueryType::AAAA);
+        break;
+    }
+
+    mdns_task_ = std::make_unique<HostResolverMdnsTask>(
+        resolver_->GetOrCreateMdnsClient(), key_.hostname, query_types);
+    mdns_task_->Start(
+        base::BindOnce(&Job::OnMdnsTaskComplete, base::Unretained(this)));
+  }
+
+  void OnMdnsTaskComplete(int error) {
+    DCHECK(is_mdns_running());
+    // TODO(crbug.com/846423): Consider adding MDNS-specific logging.
+
+    if (error != OK) {
+      CompleteRequestsWithError(error);
+    } else if (ContainsIcannNameCollisionIp(mdns_task_->result_addresses())) {
+      CompleteRequestsWithError(ERR_ICANN_NAME_COLLISION);
+    } else {
+      // MDNS uses a separate cache, so skip saving result to cache.
+      // TODO(crbug.com/846423): Consider merging caches.
+      CompleteRequestsWithoutCache(error, mdns_task_->result_addresses());
+    }
+  }
+
   URLRequestContext* url_request_context() override {
     return resolver_->url_request_context_;
   }
@@ -1880,8 +1934,12 @@
   }
 
   // Performs Job's last rites. Completes all Requests. Deletes this.
+  //
+  // If not |allow_cache|, result will not be stored in the host cache, even if
+  // result would otherwise allow doing so.
   void CompleteRequests(const HostCache::Entry& entry,
-                        base::TimeDelta ttl) {
+                        base::TimeDelta ttl,
+                        bool allow_cache) {
     CHECK(resolver_.get());
 
     // This job must be removed from resolver's |jobs_| now to make room for a
@@ -1893,6 +1951,7 @@
     if (is_running()) {
       proc_task_ = nullptr;
       KillDnsTask();
+      mdns_task_ = nullptr;
 
       // Signal dispatcher that a slot has opened.
       resolver_->dispatcher_->OnJobFinished();
@@ -1922,7 +1981,7 @@
 
     bool did_complete = (entry.error() != ERR_NETWORK_CHANGED) &&
                         (entry.error() != ERR_HOST_RESOLVER_QUEUE_TOO_LARGE);
-    if (did_complete)
+    if (did_complete && allow_cache)
       resolver_->CacheResult(key_, entry, ttl);
 
     RecordJobHistograms(entry.error());
@@ -1954,11 +2013,17 @@
     }
   }
 
+  void CompleteRequestsWithoutCache(int error, const AddressList& addresses) {
+    CompleteRequests(
+        MakeCacheEntry(error, addresses, HostCache::Entry::SOURCE_UNKNOWN),
+        base::TimeDelta(), false /* allow_cache */);
+  }
+
   // Convenience wrapper for CompleteRequests in case of failure.
   void CompleteRequestsWithError(int net_error) {
     CompleteRequests(HostCache::Entry(net_error, AddressList(),
                                       HostCache::Entry::SOURCE_UNKNOWN),
-                     base::TimeDelta());
+                     base::TimeDelta(), true /* allow_cache */);
   }
 
   RequestPriority priority() const override {
@@ -1972,6 +2037,8 @@
 
   bool is_dns_running() const { return !!dns_task_; }
 
+  bool is_mdns_running() const { return !!mdns_task_; }
+
   bool is_proc_running() const { return !!proc_task_; }
 
   base::WeakPtr<HostResolverImpl> resolver_;
@@ -2005,6 +2072,9 @@
   // Resolves the host using a DnsTransaction.
   std::unique_ptr<DnsTask> dns_task_;
 
+  // Resolves the host using MDnsClient.
+  std::unique_ptr<HostResolverMdnsTask> mdns_task_;
+
   // All Requests waiting for the result of this Job. Some can be canceled.
   base::LinkedList<RequestImpl> requests_;
 
@@ -2311,6 +2381,17 @@
   }
 }
 
+void HostResolverImpl::SetMdnsSocketFactoryForTesting(
+    std::unique_ptr<MDnsSocketFactory> socket_factory) {
+  DCHECK(!mdns_client_);
+  mdns_socket_factory_ = std::move(socket_factory);
+}
+
+void HostResolverImpl::SetMdnsClientForTesting(
+    std::unique_ptr<MDnsClient> client) {
+  mdns_client_ = std::move(client);
+}
+
 void HostResolverImpl::SetTaskRunnerForTesting(
     scoped_refptr<base::TaskRunner> task_runner) {
   proc_task_runner_ = std::move(task_runner);
@@ -2321,6 +2402,11 @@
   DCHECK(!request->job());
   // Request may only be resolved once.
   DCHECK(!request->complete());
+  // MDNS requests do not support skipping cache.
+  // TODO(crbug.com/846423): Either add support for skipping the MDNS cache, or
+  // merge to use the normal host cache for MDNS requests.
+  DCHECK(request->parameters().source != HostResolverSource::MULTICAST_DNS ||
+         request->parameters().allow_cached_response);
 
   request->set_request_time(tick_clock_->NowTicks());
 
@@ -2866,6 +2952,25 @@
                            std::abs(net_error));
 }
 
+MDnsClient* HostResolverImpl::GetOrCreateMdnsClient() {
+#if BUILDFLAG(ENABLE_MDNS)
+  if (!mdns_client_) {
+    if (!mdns_socket_factory_)
+      mdns_socket_factory_ = std::make_unique<MDnsSocketFactoryImpl>(net_log_);
+
+    mdns_client_ = MDnsClient::CreateDefault();
+    mdns_client_->StartListening(mdns_socket_factory_.get());
+  }
+
+  DCHECK(mdns_client_->IsListening());
+  return mdns_client_.get();
+#else
+  // Should not request MDNS resoltuion unless MDNS is enabled.
+  NOTREACHED();
+  return nullptr;
+#endif
+}
+
 HostResolverImpl::RequestImpl::~RequestImpl() {
   if (job_)
     job_->CancelRequest(this);