Service API for HostResolver MDNS listener

Bug: 922161
Change-Id: I33ba427d5449a0e93d36bdd9078d8a9b59b90422
Reviewed-on: https://chromium-review.googlesource.com/c/1413057
Commit-Queue: Eric Orth <ericorth@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Maks Orlovich <morlovich@chromium.org>
Cr-Commit-Position: refs/heads/master@{#625337}
diff --git a/services/network/BUILD.gn b/services/network/BUILD.gn
index 2795da5b..6a852c7d 100644
--- a/services/network/BUILD.gn
+++ b/services/network/BUILD.gn
@@ -38,6 +38,8 @@
     "empty_url_loader_client.h",
     "host_resolver.cc",
     "host_resolver.h",
+    "host_resolver_mdns_listener.cc",
+    "host_resolver_mdns_listener.h",
     "http_auth_cache_copier.cc",
     "http_auth_cache_copier.h",
     "http_cache_data_counter.cc",
diff --git a/services/network/host_resolver.cc b/services/network/host_resolver.cc
index cf52398..a9039450 100644
--- a/services/network/host_resolver.cc
+++ b/services/network/host_resolver.cc
@@ -14,6 +14,7 @@
 #include "net/dns/host_resolver.h"
 #include "net/dns/host_resolver_source.h"
 #include "net/log/net_log.h"
+#include "services/network/host_resolver_mdns_listener.h"
 #include "services/network/resolve_host_request.h"
 
 namespace network {
@@ -100,6 +101,28 @@
   DCHECK(insertion_result);
 }
 
+void HostResolver::MdnsListen(const net::HostPortPair& host,
+                              net::DnsQueryType query_type,
+                              mojom::MdnsListenClientPtr response_client,
+                              MdnsListenCallback callback) {
+#if !BUILDFLAG(ENABLE_MDNS)
+  NOTREACHED();
+#endif  // !BUILDFLAG(ENABLE_MDNS)
+
+  auto listener = std::make_unique<HostResolverMdnsListener>(internal_resolver_,
+                                                             host, query_type);
+  int rv =
+      listener->Start(std::move(response_client),
+                      base::BindOnce(&HostResolver::OnMdnsListenerCancelled,
+                                     base::Unretained(this), listener.get()));
+  if (rv == net::OK) {
+    bool insertion_result = listeners_.emplace(std::move(listener)).second;
+    DCHECK(insertion_result);
+  }
+
+  std::move(callback).Run(rv);
+}
+
 size_t HostResolver::GetNumOutstandingRequestsForTesting() const {
   return requests_.size();
 }
@@ -118,6 +141,12 @@
   requests_.erase(found_request);
 }
 
+void HostResolver::OnMdnsListenerCancelled(HostResolverMdnsListener* listener) {
+  auto found_listener = listeners_.find(listener);
+  DCHECK(found_listener != listeners_.end());
+  listeners_.erase(found_listener);
+}
+
 void HostResolver::OnConnectionError() {
   DCHECK(connection_shutdown_callback_);
 
diff --git a/services/network/host_resolver.h b/services/network/host_resolver.h
index fb4c00e7..b4629c6 100644
--- a/services/network/host_resolver.h
+++ b/services/network/host_resolver.h
@@ -7,12 +7,14 @@
 
 #include <memory>
 #include <set>
+#include <string>
 
 #include "base/callback.h"
 #include "base/component_export.h"
 #include "base/containers/unique_ptr_adapters.h"
 #include "base/macros.h"
 #include "mojo/public/cpp/bindings/binding.h"
+#include "net/dns/public/dns_query_type.h"
 #include "services/network/public/mojom/host_resolver.mojom.h"
 
 namespace net {
@@ -22,6 +24,7 @@
 }  // namespace net
 
 namespace network {
+class HostResolverMdnsListener;
 class ResolveHostRequest;
 
 class COMPONENT_EXPORT(NETWORK_SERVICE) HostResolver
@@ -47,6 +50,10 @@
   void ResolveHost(const net::HostPortPair& host,
                    mojom::ResolveHostParametersPtr optional_parameters,
                    mojom::ResolveHostClientPtr response_client) override;
+  void MdnsListen(const net::HostPortPair& host,
+                  net::DnsQueryType query_type,
+                  mojom::MdnsListenClientPtr response_client,
+                  MdnsListenCallback callback) override;
 
   size_t GetNumOutstandingRequestsForTesting() const;
 
@@ -57,12 +64,15 @@
 
  private:
   void OnResolveHostComplete(ResolveHostRequest* request, int error);
+  void OnMdnsListenerCancelled(HostResolverMdnsListener* listener);
   void OnConnectionError();
 
   mojo::Binding<mojom::HostResolver> binding_;
   ConnectionShutdownCallback connection_shutdown_callback_;
   std::set<std::unique_ptr<ResolveHostRequest>, base::UniquePtrComparator>
       requests_;
+  std::set<std::unique_ptr<HostResolverMdnsListener>, base::UniquePtrComparator>
+      listeners_;
 
   net::HostResolver* const internal_resolver_;
   net::NetLog* const net_log_;
diff --git a/services/network/host_resolver_mdns_listener.cc b/services/network/host_resolver_mdns_listener.cc
new file mode 100644
index 0000000..55732e3
--- /dev/null
+++ b/services/network/host_resolver_mdns_listener.cc
@@ -0,0 +1,88 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/network/host_resolver_mdns_listener.h"
+
+#include <utility>
+
+#include "base/callback.h"
+#include "net/base/host_port_pair.h"
+
+namespace network {
+
+HostResolverMdnsListener::HostResolverMdnsListener(
+    net::HostResolver* resolver,
+    const net::HostPortPair& host,
+    net::DnsQueryType query_type) {
+  DCHECK(resolver);
+
+  internal_listener_ = resolver->CreateMdnsListener(host, query_type);
+}
+
+HostResolverMdnsListener::~HostResolverMdnsListener() {
+  internal_listener_ = nullptr;
+  response_client_ = nullptr;
+}
+
+int HostResolverMdnsListener::Start(mojom::MdnsListenClientPtr response_client,
+                                    base::OnceClosure cancellation_callback) {
+  DCHECK(internal_listener_);
+  DCHECK(!response_client_.is_bound());
+
+  int rv = internal_listener_->Start(this);
+  if (rv != net::OK)
+    return rv;
+
+  response_client_ = std::move(response_client);
+  // Unretained |this| reference is safe because connection error cannot occur
+  // if |response_client_| goes out of scope.
+  response_client_.set_connection_error_handler(base::BindOnce(
+      &HostResolverMdnsListener::OnConnectionError, base::Unretained(this)));
+
+  cancellation_callback_ = std::move(cancellation_callback);
+
+  return net::OK;
+}
+
+void HostResolverMdnsListener::OnAddressResult(
+    net::HostResolver::MdnsListener::Delegate::UpdateType update_type,
+    net::DnsQueryType query_type,
+    net::IPEndPoint address) {
+  DCHECK(response_client_.is_bound());
+  response_client_->OnAddressResult(update_type, query_type, address);
+}
+
+void HostResolverMdnsListener::OnTextResult(
+    net::HostResolver::MdnsListener::Delegate::UpdateType update_type,
+    net::DnsQueryType query_type,
+    std::vector<std::string> text_records) {
+  DCHECK(response_client_.is_bound());
+  response_client_->OnTextResult(update_type, query_type, text_records);
+}
+
+void HostResolverMdnsListener::OnHostnameResult(
+    net::HostResolver::MdnsListener::Delegate::UpdateType update_type,
+    net::DnsQueryType query_type,
+    net::HostPortPair host) {
+  DCHECK(response_client_.is_bound());
+  response_client_->OnHostnameResult(update_type, query_type, host);
+}
+
+void HostResolverMdnsListener::OnUnhandledResult(
+    net::HostResolver::MdnsListener::Delegate::UpdateType update_type,
+    net::DnsQueryType query_type) {
+  DCHECK(response_client_.is_bound());
+  response_client_->OnUnhandledResult(update_type, query_type);
+}
+
+void HostResolverMdnsListener::OnConnectionError() {
+  DCHECK(cancellation_callback_);
+
+  internal_listener_ = nullptr;
+
+  // Invoke cancellation callback last as it may delete |this|.
+  std::move(cancellation_callback_).Run();
+}
+
+}  // namespace network
diff --git a/services/network/host_resolver_mdns_listener.h b/services/network/host_resolver_mdns_listener.h
new file mode 100644
index 0000000..7f14e25
--- /dev/null
+++ b/services/network/host_resolver_mdns_listener.h
@@ -0,0 +1,66 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_NETWORK_HOST_RESOLVER_MDNS_LISTENER_H_
+#define SERVICES_NETWORK_HOST_RESOLVER_MDNS_LISTENER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "net/base/ip_endpoint.h"
+#include "net/dns/host_resolver.h"
+#include "net/dns/public/dns_query_type.h"
+#include "services/network/public/mojom/host_resolver.mojom.h"
+
+namespace net {
+class HostPortPair;
+}  // namespace net
+
+namespace network {
+
+class HostResolverMdnsListener
+    : public net::HostResolver::MdnsListener::Delegate {
+ public:
+  HostResolverMdnsListener(net::HostResolver* resolver,
+                           const net::HostPortPair& host,
+                           net::DnsQueryType query_type);
+  ~HostResolverMdnsListener() override;
+
+  int Start(mojom::MdnsListenClientPtr response_client,
+            base::OnceClosure cancellation_callback);
+
+  // net::HostResolver::MdnsListenerDelegate implementation
+  void OnAddressResult(
+      net::HostResolver::MdnsListener::Delegate::UpdateType update_type,
+      net::DnsQueryType query_type,
+      net::IPEndPoint address) override;
+  void OnTextResult(
+      net::HostResolver::MdnsListener::Delegate::UpdateType update_type,
+      net::DnsQueryType query_type,
+      std::vector<std::string> text_records) override;
+  void OnHostnameResult(
+      net::HostResolver::MdnsListener::Delegate::UpdateType update_type,
+      net::DnsQueryType query_type,
+      net::HostPortPair host) override;
+  void OnUnhandledResult(
+      net::HostResolver::MdnsListener::Delegate::UpdateType update_type,
+      net::DnsQueryType query_type) override;
+
+ private:
+  void OnConnectionError();
+
+  std::unique_ptr<net::HostResolver::MdnsListener> internal_listener_;
+  mojom::MdnsListenClientPtr response_client_;
+
+  base::OnceClosure cancellation_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(HostResolverMdnsListener);
+};
+
+}  // namespace network
+
+#endif  // SERVICES_NETWORK_HOST_RESOLVER_MDNS_LISTENER_H_
diff --git a/services/network/host_resolver_unittest.cc b/services/network/host_resolver_unittest.cc
index 57cbc807..3980779d 100644
--- a/services/network/host_resolver_unittest.cc
+++ b/services/network/host_resolver_unittest.cc
@@ -2,8 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <memory>
-#include <string>
+#include "services/network/host_resolver.h"
+
+#include <map>
 #include <utility>
 #include <vector>
 
@@ -13,7 +14,6 @@
 #include "base/test/bind_test_util.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/test/simple_test_tick_clock.h"
-#include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "net/base/address_list.h"
 #include "net/base/host_port_pair.h"
@@ -22,13 +22,11 @@
 #include "net/base/net_errors.h"
 #include "net/dns/dns_config.h"
 #include "net/dns/dns_test_util.h"
+#include "net/dns/host_resolver.h"
 #include "net/dns/host_resolver_impl.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/dns/public/dns_protocol.h"
-#include "net/dns/public/dns_query_type.h"
 #include "net/log/net_log.h"
-#include "services/network/host_resolver.h"
-#include "services/network/public/mojom/host_resolver.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -118,6 +116,72 @@
   base::RunLoop* const run_loop_;
 };
 
+class TestMdnsListenClient : public mojom::MdnsListenClient {
+ public:
+  using UpdateType = net::HostResolver::MdnsListener::Delegate::UpdateType;
+  using UpdateKey = std::pair<UpdateType, net::DnsQueryType>;
+
+  explicit TestMdnsListenClient(mojom::MdnsListenClientPtr* interface_ptr)
+      : binding_(this, mojo::MakeRequest(interface_ptr)) {}
+
+  void OnAddressResult(UpdateType update_type,
+                       net::DnsQueryType result_type,
+                       const net::IPEndPoint& address) override {
+    address_results_.insert({{update_type, result_type}, address});
+  }
+
+  void OnTextResult(UpdateType update_type,
+                    net::DnsQueryType result_type,
+                    const std::vector<std::string>& text_records) override {
+    for (auto& text_record : text_records) {
+      text_results_.insert({{update_type, result_type}, text_record});
+    }
+  }
+
+  void OnHostnameResult(UpdateType update_type,
+                        net::DnsQueryType result_type,
+                        const net::HostPortPair& host) override {
+    hostname_results_.insert({{update_type, result_type}, host});
+  }
+
+  void OnUnhandledResult(UpdateType update_type,
+                         net::DnsQueryType result_type) override {
+    unhandled_results_.insert({update_type, result_type});
+  }
+
+  const std::multimap<UpdateKey, net::IPEndPoint>& address_results() {
+    return address_results_;
+  }
+
+  const std::multimap<UpdateKey, std::string>& text_results() {
+    return text_results_;
+  }
+
+  const std::multimap<UpdateKey, net::HostPortPair>& hostname_results() {
+    return hostname_results_;
+  }
+
+  const std::multiset<UpdateKey>& unhandled_results() {
+    return unhandled_results_;
+  }
+
+  template <typename T>
+  static std::pair<UpdateKey, T> CreateExpectedResult(
+      UpdateType update_type,
+      net::DnsQueryType query_type,
+      T result) {
+    return std::make_pair(std::make_pair(update_type, query_type), result);
+  }
+
+ private:
+  mojo::Binding<mojom::MdnsListenClient> binding_;
+
+  std::multimap<UpdateKey, net::IPEndPoint> address_results_;
+  std::multimap<UpdateKey, std::string> text_results_;
+  std::multimap<UpdateKey, net::HostPortPair> hostname_results_;
+  std::multiset<UpdateKey> unhandled_results_;
+};
+
 TEST_F(HostResolverTest, Sync) {
   auto inner_resolver = std::make_unique<net::MockHostResolver>();
   inner_resolver->set_synchronous_mode(true);
@@ -1148,5 +1212,161 @@
   EXPECT_EQ(0u, resolver.GetNumOutstandingRequestsForTesting());
 }
 
+#if BUILDFLAG(ENABLE_MDNS)
+TEST_F(HostResolverTest, MdnsListener_AddressResult) {
+  net::NetLog net_log;
+  auto inner_resolver = std::make_unique<net::MockHostResolver>();
+  HostResolver resolver(inner_resolver.get(), &net_log);
+
+  mojom::MdnsListenClientPtr response_client_ptr;
+  TestMdnsListenClient response_client(&response_client_ptr);
+
+  int error = net::ERR_FAILED;
+  base::RunLoop run_loop;
+  net::HostPortPair host("host.local", 41);
+  resolver.MdnsListen(host, net::DnsQueryType::A,
+                      std::move(response_client_ptr),
+                      base::BindLambdaForTesting([&](int error_val) {
+                        error = error_val;
+                        run_loop.Quit();
+                      }));
+
+  run_loop.Run();
+  ASSERT_EQ(net::OK, error);
+
+  net::IPAddress result_address(1, 2, 3, 4);
+  net::IPEndPoint result(result_address, 41);
+  inner_resolver->TriggerMdnsListeners(
+      host, net::DnsQueryType::A,
+      net::HostResolver::MdnsListener::Delegate::UpdateType::ADDED, result);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_THAT(response_client.address_results(),
+              testing::ElementsAre(TestMdnsListenClient::CreateExpectedResult(
+                  net::HostResolver::MdnsListener::Delegate::UpdateType::ADDED,
+                  net::DnsQueryType::A, result)));
+
+  EXPECT_THAT(response_client.text_results(), testing::IsEmpty());
+  EXPECT_THAT(response_client.hostname_results(), testing::IsEmpty());
+  EXPECT_THAT(response_client.unhandled_results(), testing::IsEmpty());
+}
+
+TEST_F(HostResolverTest, MdnsListener_TextResult) {
+  net::NetLog net_log;
+  auto inner_resolver = std::make_unique<net::MockHostResolver>();
+  HostResolver resolver(inner_resolver.get(), &net_log);
+
+  mojom::MdnsListenClientPtr response_client_ptr;
+  TestMdnsListenClient response_client(&response_client_ptr);
+
+  int error = net::ERR_FAILED;
+  base::RunLoop run_loop;
+  net::HostPortPair host("host.local", 42);
+  resolver.MdnsListen(host, net::DnsQueryType::TXT,
+                      std::move(response_client_ptr),
+                      base::BindLambdaForTesting([&](int error_val) {
+                        error = error_val;
+                        run_loop.Quit();
+                      }));
+
+  run_loop.Run();
+  ASSERT_EQ(net::OK, error);
+
+  inner_resolver->TriggerMdnsListeners(
+      host, net::DnsQueryType::TXT,
+      net::HostResolver::MdnsListener::Delegate::UpdateType::CHANGED,
+      {"foo", "bar"});
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_THAT(
+      response_client.text_results(),
+      testing::UnorderedElementsAre(
+          TestMdnsListenClient::CreateExpectedResult(
+              net::HostResolver::MdnsListener::Delegate::UpdateType::CHANGED,
+              net::DnsQueryType::TXT, "foo"),
+          TestMdnsListenClient::CreateExpectedResult(
+              net::HostResolver::MdnsListener::Delegate::UpdateType::CHANGED,
+              net::DnsQueryType::TXT, "bar")));
+
+  EXPECT_THAT(response_client.address_results(), testing::IsEmpty());
+  EXPECT_THAT(response_client.hostname_results(), testing::IsEmpty());
+  EXPECT_THAT(response_client.unhandled_results(), testing::IsEmpty());
+}
+
+TEST_F(HostResolverTest, MdnsListener_HostnameResult) {
+  net::NetLog net_log;
+  auto inner_resolver = std::make_unique<net::MockHostResolver>();
+  HostResolver resolver(inner_resolver.get(), &net_log);
+
+  mojom::MdnsListenClientPtr response_client_ptr;
+  TestMdnsListenClient response_client(&response_client_ptr);
+
+  int error = net::ERR_FAILED;
+  base::RunLoop run_loop;
+  net::HostPortPair host("host.local", 43);
+  resolver.MdnsListen(host, net::DnsQueryType::PTR,
+                      std::move(response_client_ptr),
+                      base::BindLambdaForTesting([&](int error_val) {
+                        error = error_val;
+                        run_loop.Quit();
+                      }));
+
+  run_loop.Run();
+  ASSERT_EQ(net::OK, error);
+
+  net::HostPortPair result("example.com", 43);
+  inner_resolver->TriggerMdnsListeners(
+      host, net::DnsQueryType::PTR,
+      net::HostResolver::MdnsListener::Delegate::UpdateType::REMOVED, result);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_THAT(
+      response_client.hostname_results(),
+      testing::ElementsAre(TestMdnsListenClient::CreateExpectedResult(
+          net::HostResolver::MdnsListener::Delegate::UpdateType::REMOVED,
+          net::DnsQueryType::PTR, result)));
+
+  EXPECT_THAT(response_client.address_results(), testing::IsEmpty());
+  EXPECT_THAT(response_client.text_results(), testing::IsEmpty());
+  EXPECT_THAT(response_client.unhandled_results(), testing::IsEmpty());
+}
+
+TEST_F(HostResolverTest, MdnsListener_UnhandledResult) {
+  net::NetLog net_log;
+  auto inner_resolver = std::make_unique<net::MockHostResolver>();
+  HostResolver resolver(inner_resolver.get(), &net_log);
+
+  mojom::MdnsListenClientPtr response_client_ptr;
+  TestMdnsListenClient response_client(&response_client_ptr);
+
+  int error = net::ERR_FAILED;
+  base::RunLoop run_loop;
+  net::HostPortPair host("host.local", 44);
+  resolver.MdnsListen(host, net::DnsQueryType::PTR,
+                      std::move(response_client_ptr),
+                      base::BindLambdaForTesting([&](int error_val) {
+                        error = error_val;
+                        run_loop.Quit();
+                      }));
+
+  run_loop.Run();
+  ASSERT_EQ(net::OK, error);
+
+  inner_resolver->TriggerMdnsListeners(
+      host, net::DnsQueryType::PTR,
+      net::HostResolver::MdnsListener::Delegate::UpdateType::ADDED);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_THAT(response_client.unhandled_results(),
+              testing::ElementsAre(std::make_pair(
+                  net::HostResolver::MdnsListener::Delegate::UpdateType::ADDED,
+                  net::DnsQueryType::PTR)));
+
+  EXPECT_THAT(response_client.address_results(), testing::IsEmpty());
+  EXPECT_THAT(response_client.text_results(), testing::IsEmpty());
+  EXPECT_THAT(response_client.hostname_results(), testing::IsEmpty());
+}
+#endif  // BUILDFLAG(ENABLE_MDNS)
+
 }  // namespace
 }  // namespace network
diff --git a/services/network/public/cpp/host_resolver.typemap b/services/network/public/cpp/host_resolver.typemap
index 8921967..20cb2428 100644
--- a/services/network/public/cpp/host_resolver.typemap
+++ b/services/network/public/cpp/host_resolver.typemap
@@ -5,6 +5,7 @@
 mojom = "//services/network/public/mojom/host_resolver.mojom"
 public_headers = [
   "//net/dns/dns_config_overrides.h",
+  "//net/dns/host_resolver.h",
   "//net/dns/host_resolver_source.h",
   "//net/dns/public/dns_query_type.h",
 ]
@@ -18,6 +19,7 @@
 ]
 type_mappings = [
   "network.mojom.DnsConfigOverrides=net::DnsConfigOverrides",
-  "network.mojom.ResolveHostParameters.DnsQueryType=net::DnsQueryType",
+  "network.mojom.DnsQueryType=net::DnsQueryType",
   "network.mojom.ResolveHostParameters.Source=net::HostResolverSource",
+  "network.mojom.MdnsListenClient.UpdateType=net::HostResolver::MdnsListener::Delegate::UpdateType",
 ]
diff --git a/services/network/public/cpp/host_resolver_mojom_traits.cc b/services/network/public/cpp/host_resolver_mojom_traits.cc
index 78a22a7..424cba4 100644
--- a/services/network/public/cpp/host_resolver_mojom_traits.cc
+++ b/services/network/public/cpp/host_resolver_mojom_traits.cc
@@ -18,6 +18,8 @@
 using network::mojom::DnsOverHttpsServer;
 using network::mojom::DnsOverHttpsServerDataView;
 using network::mojom::DnsOverHttpsServerPtr;
+using network::mojom::DnsQueryType;
+using network::mojom::MdnsListenClient;
 using network::mojom::ResolveHostParameters;
 
 namespace {
@@ -215,46 +217,45 @@
 }
 
 // static
-ResolveHostParameters::DnsQueryType
-EnumTraits<ResolveHostParameters::DnsQueryType, net::DnsQueryType>::ToMojom(
+DnsQueryType EnumTraits<DnsQueryType, net::DnsQueryType>::ToMojom(
     net::DnsQueryType input) {
   switch (input) {
     case net::DnsQueryType::UNSPECIFIED:
-      return ResolveHostParameters::DnsQueryType::UNSPECIFIED;
+      return DnsQueryType::UNSPECIFIED;
     case net::DnsQueryType::A:
-      return ResolveHostParameters::DnsQueryType::A;
+      return DnsQueryType::A;
     case net::DnsQueryType::AAAA:
-      return ResolveHostParameters::DnsQueryType::AAAA;
+      return DnsQueryType::AAAA;
     case net::DnsQueryType::TXT:
-      return ResolveHostParameters::DnsQueryType::TXT;
+      return DnsQueryType::TXT;
     case net::DnsQueryType::PTR:
-      return ResolveHostParameters::DnsQueryType::PTR;
+      return DnsQueryType::PTR;
     case net::DnsQueryType::SRV:
-      return ResolveHostParameters::DnsQueryType::SRV;
+      return DnsQueryType::SRV;
   }
 }
 
 // static
-bool EnumTraits<ResolveHostParameters::DnsQueryType, net::DnsQueryType>::
-    FromMojom(ResolveHostParameters::DnsQueryType input,
-              net::DnsQueryType* output) {
+bool EnumTraits<DnsQueryType, net::DnsQueryType>::FromMojom(
+    DnsQueryType input,
+    net::DnsQueryType* output) {
   switch (input) {
-    case ResolveHostParameters::DnsQueryType::UNSPECIFIED:
+    case DnsQueryType::UNSPECIFIED:
       *output = net::DnsQueryType::UNSPECIFIED;
       return true;
-    case ResolveHostParameters::DnsQueryType::A:
+    case DnsQueryType::A:
       *output = net::DnsQueryType::A;
       return true;
-    case ResolveHostParameters::DnsQueryType::AAAA:
+    case DnsQueryType::AAAA:
       *output = net::DnsQueryType::AAAA;
       return true;
-    case ResolveHostParameters::DnsQueryType::TXT:
+    case DnsQueryType::TXT:
       *output = net::DnsQueryType::TXT;
       return true;
-    case ResolveHostParameters::DnsQueryType::PTR:
+    case DnsQueryType::PTR:
       *output = net::DnsQueryType::PTR;
       return true;
-    case ResolveHostParameters::DnsQueryType::SRV:
+    case DnsQueryType::SRV:
       *output = net::DnsQueryType::SRV;
       return true;
   }
@@ -296,4 +297,37 @@
   }
 }
 
+// static
+MdnsListenClient::UpdateType
+EnumTraits<MdnsListenClient::UpdateType,
+           net::HostResolver::MdnsListener::Delegate::UpdateType>::
+    ToMojom(net::HostResolver::MdnsListener::Delegate::UpdateType input) {
+  switch (input) {
+    case net::HostResolver::MdnsListener::Delegate::UpdateType::ADDED:
+      return MdnsListenClient::UpdateType::ADDED;
+    case net::HostResolver::MdnsListener::Delegate::UpdateType::CHANGED:
+      return MdnsListenClient::UpdateType::CHANGED;
+    case net::HostResolver::MdnsListener::Delegate::UpdateType::REMOVED:
+      return MdnsListenClient::UpdateType::REMOVED;
+  }
+}
+
+// static
+bool EnumTraits<MdnsListenClient::UpdateType,
+                net::HostResolver::MdnsListener::Delegate::UpdateType>::
+    FromMojom(MdnsListenClient::UpdateType input,
+              net::HostResolver::MdnsListener::Delegate::UpdateType* output) {
+  switch (input) {
+    case MdnsListenClient::UpdateType::ADDED:
+      *output = net::HostResolver::MdnsListener::Delegate::UpdateType::ADDED;
+      return true;
+    case MdnsListenClient::UpdateType::CHANGED:
+      *output = net::HostResolver::MdnsListener::Delegate::UpdateType::CHANGED;
+      return true;
+    case MdnsListenClient::UpdateType::REMOVED:
+      *output = net::HostResolver::MdnsListener::Delegate::UpdateType::REMOVED;
+      return true;
+  }
+}
+
 }  // namespace mojo
diff --git a/services/network/public/cpp/host_resolver_mojom_traits.h b/services/network/public/cpp/host_resolver_mojom_traits.h
index 3d18769a..3a5fd42 100644
--- a/services/network/public/cpp/host_resolver_mojom_traits.h
+++ b/services/network/public/cpp/host_resolver_mojom_traits.h
@@ -72,13 +72,10 @@
 };
 
 template <>
-struct EnumTraits<network::mojom::ResolveHostParameters::DnsQueryType,
-                  net::DnsQueryType> {
-  static network::mojom::ResolveHostParameters::DnsQueryType ToMojom(
-      net::DnsQueryType input);
-  static bool FromMojom(
-      network::mojom::ResolveHostParameters::DnsQueryType input,
-      net::DnsQueryType* output);
+struct EnumTraits<network::mojom::DnsQueryType, net::DnsQueryType> {
+  static network::mojom::DnsQueryType ToMojom(net::DnsQueryType input);
+  static bool FromMojom(network::mojom::DnsQueryType input,
+                        net::DnsQueryType* output);
 };
 
 template <>
@@ -90,6 +87,16 @@
                         net::HostResolverSource* output);
 };
 
+template <>
+struct EnumTraits<network::mojom::MdnsListenClient::UpdateType,
+                  net::HostResolver::MdnsListener::Delegate::UpdateType> {
+  static network::mojom::MdnsListenClient::UpdateType ToMojom(
+      net::HostResolver::MdnsListener::Delegate::UpdateType input);
+  static bool FromMojom(
+      network::mojom::MdnsListenClient::UpdateType input,
+      net::HostResolver::MdnsListener::Delegate::UpdateType* output);
+};
+
 }  // namespace mojo
 
 #endif  // SERVICES_NETWORK_PUBLIC_CPP_HOST_RESOLVER_MOJOM_TRAITS_H_
diff --git a/services/network/public/mojom/host_resolver.mojom b/services/network/public/mojom/host_resolver.mojom
index b3e723f1..3fe9011e 100644
--- a/services/network/public/mojom/host_resolver.mojom
+++ b/services/network/public/mojom/host_resolver.mojom
@@ -118,22 +118,22 @@
   OnHostnameResults(array<HostPortPair> hosts);
 };
 
+// DNS query type for a ResolveHostRequest.
+// See:
+// https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4
+enum DnsQueryType {
+  UNSPECIFIED,
+  A,
+  AAAA,
+  TXT,
+  PTR,
+  SRV,
+};
+
 // Parameter-grouping struct for additional optional parameters for
 // HostResolver::ResolveHost() calls. All fields are optional and have a
 // reasonable default.
 struct ResolveHostParameters {
-  // DNS query type for a ResolveHostRequest.
-  // See:
-  // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4
-  enum DnsQueryType {
-    UNSPECIFIED,
-    A,
-    AAAA,
-    TXT,
-    PTR,
-    SRV,
-  };
-
   // Requested DNS query type. If UNSPECIFIED, resolver will pick A or AAAA (or
   // both) based on IPv4/IPv6 settings.
   DnsQueryType dns_query_type = DnsQueryType.UNSPECIFIED;
@@ -189,9 +189,36 @@
   bool is_speculative = false;
 };
 
+// Response interface used to receive notifications from
+// HostResolver::MdnsListen requests. All methods have a |query_type| field to
+// allow a single BindingSet and implementation to be used to listen for updates
+// for multiple types for the same host.
+interface MdnsListenClient {
+  enum UpdateType {
+    ADDED,
+    CHANGED,
+    REMOVED
+  };
+
+  OnAddressResult(UpdateType update_type,
+                  DnsQueryType query_type,
+                  IPEndPoint endpoint);
+  OnTextResult(UpdateType update_type,
+               DnsQueryType query_type,
+               array<string> text_records);
+  OnHostnameResult(UpdateType update_type,
+                   DnsQueryType query_type,
+                   HostPortPair host);
+
+  // For results which may be valid MDNS but are not handled/parsed by network
+  // service, e.g. pointers to the root domain.
+  OnUnhandledResult(UpdateType update_type, DnsQueryType query_type);
+};
+
 // Interface that can be passed to code/processes without direct access to
 // NetworkContext to make ResolveHost requests. If destroyed, all outstanding
-// ResolveHost requests from the destroyed interface will be cancelled.
+// ResolveHost and MdnsListen requests from the destroyed interface will be
+// cancelled.
 interface HostResolver {
   // Resolves the given hostname (or IP address literal). Results are a network
   // error code, and on success (network error code OK), an AddressList. All
@@ -214,6 +241,17 @@
   ResolveHost(HostPortPair host,
               ResolveHostParameters? optional_parameters,
               ResolveHostClient response_client);
+
+  // Starts a listener to watch for updates to a multicast DNS result. Result is
+  // a network error code indicating the success of starting the listener. On
+  // success (result OK), |response_client| will begin receiving update
+  // notifications.
+  //
+  // All outstanding listeners are cancelled and will receive no further
+  // notifications if the HostResolver or parent NetworkContext are destroyed.
+  MdnsListen(HostPortPair host,
+             DnsQueryType query_type,
+             MdnsListenClient response_client) => (int32 result);
 };
 
 // A client interface that subscribes to DNS config change events from