| // Copyright 2014 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 "chrome/browser/local_discovery/service_discovery_client_mdns.h" |
| |
| #include <stddef.h> |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/macros.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/task/post_task.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/local_discovery/service_discovery_client_impl.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/network_service_instance.h" |
| #include "net/base/net_errors.h" |
| #include "net/socket/datagram_server_socket.h" |
| |
| namespace net { |
| class IPAddress; |
| } |
| |
| namespace local_discovery { |
| |
| using content::BrowserThread; |
| |
| // Base class for objects returned by ServiceDiscoveryClient implementation. |
| // Handles interaction of client code on UI thread end net code on mdns thread. |
| class ServiceDiscoveryClientMdns::Proxy { |
| public: |
| using WeakPtr = base::WeakPtr<Proxy>; |
| |
| explicit Proxy(ServiceDiscoveryClientMdns* client) |
| : client_(client), |
| weak_ptr_factory_(this) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| client_->proxies_.AddObserver(this); |
| } |
| |
| virtual ~Proxy() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| client_->proxies_.RemoveObserver(this); |
| } |
| |
| // Returns true if object is not yet shutdown. |
| virtual bool IsValid() = 0; |
| |
| // Notifies proxies that mDNS layer is going to be destroyed. |
| virtual void OnMdnsDestroy() = 0; |
| |
| // Notifies proxies that new mDNS instance is ready. |
| virtual void OnNewMdnsReady() { |
| DCHECK(!client_->need_delay_mdns_tasks_); |
| if (IsValid()) { |
| for (const auto& task : delayed_tasks_) |
| client_->mdns_runner_->PostTask(FROM_HERE, task); |
| } |
| delayed_tasks_.clear(); |
| } |
| |
| // Runs callback using this method to abort callback if instance of |Proxy| |
| // is deleted. |
| void RunCallback(base::OnceClosure callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| std::move(callback).Run(); |
| } |
| |
| protected: |
| void PostToMdnsThread(const base::Closure& task) { |
| DCHECK(IsValid()); |
| // The first task on the IO thread for each |mdns_| instance must be |
| // InitMdns(). OnInterfaceListReady() could be delayed by |
| // GetMDnsInterfacesToBind() running on a background task runner, so |
| // PostToMdnsThread() could be called to post task for |mdns_| that is not |
| // initialized yet. |
| if (!client_->need_delay_mdns_tasks_) { |
| client_->mdns_runner_->PostTask(FROM_HERE, task); |
| return; |
| } |
| delayed_tasks_.push_back(task); |
| } |
| |
| static bool PostToUIThread(base::OnceClosure task) { |
| return base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, |
| std::move(task)); |
| } |
| |
| ServiceDiscoveryClient* client() { |
| return client_->client_.get(); |
| } |
| |
| WeakPtr GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| template<class T> |
| void DeleteOnMdnsThread(T* t) { |
| if (!t) |
| return; |
| if (!client_->mdns_runner_->DeleteSoon(FROM_HERE, t)) |
| delete t; |
| } |
| |
| private: |
| scoped_refptr<ServiceDiscoveryClientMdns> client_; |
| // Delayed |mdns_runner_| tasks. |
| std::vector<base::Closure> delayed_tasks_; |
| base::WeakPtrFactory<Proxy> weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Proxy); |
| }; |
| |
| namespace { |
| |
| const int kMaxRestartAttempts = 10; |
| const int kRestartDelayOnNetworkChangeSeconds = 3; |
| |
| using MdnsInitCallback = base::Callback<void(int)>; |
| |
| class SocketFactory : public net::MDnsSocketFactory { |
| public: |
| explicit SocketFactory(const net::InterfaceIndexFamilyList& interfaces) |
| : interfaces_(interfaces) {} |
| |
| // net::MDnsSocketFactory implementation: |
| void CreateSockets(std::vector<std::unique_ptr<net::DatagramServerSocket>>* |
| sockets) override { |
| for (size_t i = 0; i < interfaces_.size(); ++i) { |
| DCHECK(interfaces_[i].second == net::ADDRESS_FAMILY_IPV4 || |
| interfaces_[i].second == net::ADDRESS_FAMILY_IPV6); |
| std::unique_ptr<net::DatagramServerSocket> socket(CreateAndBindMDnsSocket( |
| interfaces_[i].second, interfaces_[i].first, nullptr /* net_log */)); |
| if (socket) |
| sockets->push_back(std::move(socket)); |
| } |
| } |
| |
| private: |
| net::InterfaceIndexFamilyList interfaces_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SocketFactory); |
| }; |
| |
| void InitMdns(const MdnsInitCallback& on_initialized, |
| const net::InterfaceIndexFamilyList& interfaces, |
| net::MDnsClient* mdns) { |
| SocketFactory socket_factory(interfaces); |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(on_initialized, mdns->StartListening(&socket_factory))); |
| } |
| |
| template<class T> |
| class ProxyBase : public ServiceDiscoveryClientMdns::Proxy, public T { |
| public: |
| using Base = ProxyBase<T>; |
| |
| explicit ProxyBase(ServiceDiscoveryClientMdns* client) |
| : Proxy(client) { |
| } |
| |
| ~ProxyBase() override { |
| DeleteOnMdnsThread(implementation_.release()); |
| } |
| |
| bool IsValid() override { |
| return !!implementation(); |
| } |
| |
| void OnMdnsDestroy() override { |
| DeleteOnMdnsThread(implementation_.release()); |
| } |
| |
| protected: |
| void set_implementation(std::unique_ptr<T> implementation) { |
| implementation_ = std::move(implementation); |
| } |
| |
| T* implementation() const { |
| return implementation_.get(); |
| } |
| |
| private: |
| std::unique_ptr<T> implementation_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ProxyBase); |
| }; |
| |
| class ServiceWatcherProxy : public ProxyBase<ServiceWatcher> { |
| public: |
| ServiceWatcherProxy(ServiceDiscoveryClientMdns* client_mdns, |
| const std::string& service_type, |
| const ServiceWatcher::UpdatedCallback& callback) |
| : ProxyBase(client_mdns), |
| service_type_(service_type), |
| callback_(callback) { |
| // It's safe to call |CreateServiceWatcher| on UI thread, because |
| // |MDnsClient| is not used there. It's simplify implementation. |
| set_implementation(client()->CreateServiceWatcher( |
| service_type, |
| base::Bind(&ServiceWatcherProxy::OnCallback, GetWeakPtr(), callback))); |
| } |
| |
| // ServiceWatcher methods. |
| void Start() override { |
| if (implementation()) { |
| PostToMdnsThread(base::Bind(&ServiceWatcher::Start, |
| base::Unretained(implementation()))); |
| } |
| } |
| |
| void DiscoverNewServices() override { |
| if (implementation()) { |
| PostToMdnsThread(base::Bind(&ServiceWatcher::DiscoverNewServices, |
| base::Unretained(implementation()))); |
| } |
| } |
| |
| void SetActivelyRefreshServices(bool actively_refresh_services) override { |
| if (implementation()) { |
| PostToMdnsThread(base::Bind(&ServiceWatcher::SetActivelyRefreshServices, |
| base::Unretained(implementation()), |
| actively_refresh_services)); |
| } |
| } |
| |
| std::string GetServiceType() const override { return service_type_; } |
| |
| void OnNewMdnsReady() override { |
| ProxyBase<ServiceWatcher>::OnNewMdnsReady(); |
| if (!implementation()) |
| callback_.Run(ServiceWatcher::UPDATE_INVALIDATED, ""); |
| } |
| |
| private: |
| static void OnCallback(const WeakPtr& proxy, |
| const ServiceWatcher::UpdatedCallback& callback, |
| UpdateType a1, |
| const std::string& a2) { |
| DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| PostToUIThread(base::Bind(&Base::RunCallback, proxy, |
| base::Bind(callback, a1, a2))); |
| } |
| std::string service_type_; |
| ServiceWatcher::UpdatedCallback callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ServiceWatcherProxy); |
| }; |
| |
| class ServiceResolverProxy : public ProxyBase<ServiceResolver> { |
| public: |
| ServiceResolverProxy(ServiceDiscoveryClientMdns* client_mdns, |
| const std::string& service_name, |
| ServiceResolver::ResolveCompleteCallback callback) |
| : ProxyBase(client_mdns), service_name_(service_name) { |
| // It's safe to call |CreateServiceResolver| on UI thread, because |
| // |MDnsClient| is not used there. It's simplify implementation. |
| set_implementation(client()->CreateServiceResolver( |
| service_name, base::BindOnce(&ServiceResolverProxy::OnCallback, |
| GetWeakPtr(), std::move(callback)))); |
| } |
| |
| // ServiceResolver methods. |
| void StartResolving() override { |
| if (implementation()) { |
| PostToMdnsThread(base::Bind(&ServiceResolver::StartResolving, |
| base::Unretained(implementation()))); |
| } |
| } |
| |
| std::string GetName() const override { return service_name_; } |
| |
| private: |
| static void OnCallback(const WeakPtr& proxy, |
| ServiceResolver::ResolveCompleteCallback callback, |
| RequestStatus a1, |
| const ServiceDescription& a2) { |
| DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| PostToUIThread(base::BindOnce(&Base::RunCallback, proxy, |
| base::BindOnce(std::move(callback), a1, a2))); |
| } |
| |
| std::string service_name_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ServiceResolverProxy); |
| }; |
| |
| class LocalDomainResolverProxy : public ProxyBase<LocalDomainResolver> { |
| public: |
| LocalDomainResolverProxy(ServiceDiscoveryClientMdns* client_mdns, |
| const std::string& domain, |
| net::AddressFamily address_family, |
| LocalDomainResolver::IPAddressCallback callback) |
| : ProxyBase(client_mdns) { |
| // It's safe to call |CreateLocalDomainResolver| on UI thread, because |
| // |MDnsClient| is not used there. It's simplify implementation. |
| set_implementation(client()->CreateLocalDomainResolver( |
| domain, address_family, |
| base::BindOnce(&LocalDomainResolverProxy::OnCallback, GetWeakPtr(), |
| std::move(callback)))); |
| } |
| |
| // LocalDomainResolver methods. |
| void Start() override { |
| if (implementation()) { |
| PostToMdnsThread(base::Bind(&LocalDomainResolver::Start, |
| base::Unretained(implementation()))); |
| } |
| } |
| |
| private: |
| static void OnCallback(const WeakPtr& proxy, |
| LocalDomainResolver::IPAddressCallback callback, |
| bool a1, |
| const net::IPAddress& a2, |
| const net::IPAddress& a3) { |
| DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| PostToUIThread( |
| base::BindOnce(&Base::RunCallback, proxy, |
| base::BindOnce(std::move(callback), a1, a2, a3))); |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(LocalDomainResolverProxy); |
| }; |
| |
| } // namespace |
| |
| ServiceDiscoveryClientMdns::ServiceDiscoveryClientMdns() |
| : mdns_runner_( |
| base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO})), |
| restart_attempts_(0), |
| need_delay_mdns_tasks_(true), |
| weak_ptr_factory_(this) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this); |
| StartNewClient(); |
| } |
| |
| std::unique_ptr<ServiceWatcher> |
| ServiceDiscoveryClientMdns::CreateServiceWatcher( |
| const std::string& service_type, |
| const ServiceWatcher::UpdatedCallback& callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| return std::make_unique<ServiceWatcherProxy>(this, service_type, callback); |
| } |
| |
| std::unique_ptr<ServiceResolver> |
| ServiceDiscoveryClientMdns::CreateServiceResolver( |
| const std::string& service_name, |
| ServiceResolver::ResolveCompleteCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| return std::make_unique<ServiceResolverProxy>(this, service_name, |
| std::move(callback)); |
| } |
| |
| std::unique_ptr<LocalDomainResolver> |
| ServiceDiscoveryClientMdns::CreateLocalDomainResolver( |
| const std::string& domain, |
| net::AddressFamily address_family, |
| LocalDomainResolver::IPAddressCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| return std::make_unique<LocalDomainResolverProxy>( |
| this, domain, address_family, std::move(callback)); |
| } |
| |
| ServiceDiscoveryClientMdns::~ServiceDiscoveryClientMdns() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(this); |
| DestroyMdns(); |
| } |
| |
| void ServiceDiscoveryClientMdns::OnConnectionChanged( |
| network::mojom::ConnectionType type) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| // Only network changes resets counter. |
| restart_attempts_ = 0; |
| ScheduleStartNewClient(); |
| } |
| |
| void ServiceDiscoveryClientMdns::ScheduleStartNewClient() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| OnBeforeMdnsDestroy(); |
| if (restart_attempts_ >= kMaxRestartAttempts) { |
| ReportSuccess(); |
| return; |
| } |
| |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&ServiceDiscoveryClientMdns::StartNewClient, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::TimeDelta::FromSeconds(kRestartDelayOnNetworkChangeSeconds * |
| (1 << restart_attempts_))); |
| } |
| |
| void ServiceDiscoveryClientMdns::StartNewClient() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| ++restart_attempts_; |
| DestroyMdns(); |
| mdns_ = net::MDnsClient::CreateDefault(); |
| client_ = std::make_unique<ServiceDiscoveryClientImpl>(mdns_.get()); |
| base::PostTaskWithTraitsAndReplyWithResult( |
| FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()}, |
| base::BindOnce(&net::GetMDnsInterfacesToBind), |
| base::BindOnce(&ServiceDiscoveryClientMdns::OnInterfaceListReady, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ServiceDiscoveryClientMdns::OnInterfaceListReady( |
| const net::InterfaceIndexFamilyList& interfaces) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| mdns_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&InitMdns, |
| base::Bind(&ServiceDiscoveryClientMdns::OnMdnsInitialized, |
| weak_ptr_factory_.GetWeakPtr()), |
| interfaces, base::Unretained(mdns_.get()))); |
| } |
| |
| void ServiceDiscoveryClientMdns::OnMdnsInitialized(int net_error) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (net_error != net::OK) { |
| ScheduleStartNewClient(); |
| return; |
| } |
| ReportSuccess(); |
| |
| // Initialization is done, no need to delay tasks. |
| need_delay_mdns_tasks_ = false; |
| for (Proxy& observer : proxies_) |
| observer.OnNewMdnsReady(); |
| } |
| |
| void ServiceDiscoveryClientMdns::ReportSuccess() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| UMA_HISTOGRAM_COUNTS_100("LocalDiscovery.ClientRestartAttempts", |
| restart_attempts_); |
| } |
| |
| void ServiceDiscoveryClientMdns::OnBeforeMdnsDestroy() { |
| need_delay_mdns_tasks_ = true; |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| for (Proxy& observer : proxies_) |
| observer.OnMdnsDestroy(); |
| } |
| |
| void ServiceDiscoveryClientMdns::DestroyMdns() { |
| OnBeforeMdnsDestroy(); |
| // After calling Proxy::OnMdnsDestroy(), all references to |client_| and |
| // |mdns_| should be destroyed. |
| if (client_) |
| mdns_runner_->DeleteSoon(FROM_HERE, client_.release()); |
| if (mdns_) |
| mdns_runner_->DeleteSoon(FROM_HERE, mdns_.release()); |
| } |
| |
| } // namespace local_discovery |