blob: cfaa3cf0e6f6d6f3f6d0a368cde76347a44b0277 [file] [log] [blame]
// 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 "base/memory/scoped_vector.h"
#include "base/metrics/histogram.h"
#include "chrome/common/local_discovery/service_discovery_client_impl.h"
#include "content/public/browser/browser_thread.h"
#include "net/dns/mdns_client.h"
#include "net/udp/datagram_server_socket.h"
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:
typedef base::WeakPtr<Proxy> WeakPtr;
explicit Proxy(ServiceDiscoveryClientMdns* client)
: client_(client),
weak_ptr_factory_(this) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
client_->proxies_.AddObserver(this);
}
virtual ~Proxy() {
DCHECK(BrowserThread::CurrentlyOn(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_dalay_mdns_tasks_);
if (IsValid()) {
for (size_t i = 0; i < delayed_tasks_.size(); ++i)
client_->mdns_runner_->PostTask(FROM_HERE, delayed_tasks_[i]);
}
delayed_tasks_.clear();
}
// Runs callback using this method to abort callback if instance of |Proxy|
// is deleted.
void RunCallback(const base::Closure& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
callback.Run();
}
protected:
void PostToMdnsThread(const base::Closure& task) {
DCHECK(IsValid());
// The first task on IO thread for each |mdns_| instance must be |InitMdns|.
// |OnInterfaceListReady| could be delayed by |GetMDnsInterfacesToBind|
// running on FILE thread, so |PostToMdnsThread| could be called to post
// task for |mdns_| that is not initialized yet.
if (!client_->need_dalay_mdns_tasks_) {
client_->mdns_runner_->PostTask(FROM_HERE, task);
return;
}
delayed_tasks_.push_back(task);
}
static bool PostToUIThread(const base::Closure& task) {
return BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 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;
typedef base::Callback<void(bool)> MdnsInitCallback;
class SocketFactory : public net::MDnsSocketFactory {
public:
explicit SocketFactory(const net::InterfaceIndexFamilyList& interfaces)
: interfaces_(interfaces) {}
// net::MDnsSocketFactory implementation:
void CreateSockets(
ScopedVector<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);
scoped_ptr<net::DatagramServerSocket> socket(
CreateAndBindMDnsSocket(interfaces_[i].second, interfaces_[i].first));
if (socket)
sockets->push_back(socket.release());
}
}
private:
net::InterfaceIndexFamilyList interfaces_;
};
void InitMdns(const MdnsInitCallback& on_initialized,
const net::InterfaceIndexFamilyList& interfaces,
net::MDnsClient* mdns) {
SocketFactory socket_factory(interfaces);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(on_initialized,
mdns->StartListening(&socket_factory)));
}
template<class T>
class ProxyBase : public ServiceDiscoveryClientMdns::Proxy, public T {
public:
typedef ProxyBase<T> Base;
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(scoped_ptr<T> implementation) {
implementation_ = implementation.Pass();
}
T* implementation() const {
return implementation_.get();
}
private:
scoped_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(bool force_update) override {
if (implementation()) {
PostToMdnsThread(base::Bind(&ServiceWatcher::DiscoverNewServices,
base::Unretained(implementation()),
force_update));
}
}
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,
const 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::Bind(&ServiceResolverProxy::OnCallback, GetWeakPtr(), 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,
const ServiceResolver::ResolveCompleteCallback& callback,
RequestStatus a1,
const ServiceDescription& a2) {
DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
PostToUIThread(base::Bind(&Base::RunCallback, proxy,
base::Bind(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,
const 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::Bind(
&LocalDomainResolverProxy::OnCallback, GetWeakPtr(), callback)));
}
// LocalDomainResolver methods.
void Start() override {
if (implementation()) {
PostToMdnsThread(base::Bind(&LocalDomainResolver::Start,
base::Unretained(implementation())));
}
};
private:
static void OnCallback(const WeakPtr& proxy,
const LocalDomainResolver::IPAddressCallback& callback,
bool a1,
const net::IPAddressNumber& a2,
const net::IPAddressNumber& a3) {
DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
PostToUIThread(base::Bind(&Base::RunCallback, proxy,
base::Bind(callback, a1, a2, a3)));
}
DISALLOW_COPY_AND_ASSIGN(LocalDomainResolverProxy);
};
} // namespace
ServiceDiscoveryClientMdns::ServiceDiscoveryClientMdns()
: mdns_runner_(
BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO)),
restart_attempts_(0),
need_dalay_mdns_tasks_(true),
weak_ptr_factory_(this) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
StartNewClient();
}
scoped_ptr<ServiceWatcher> ServiceDiscoveryClientMdns::CreateServiceWatcher(
const std::string& service_type,
const ServiceWatcher::UpdatedCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
return scoped_ptr<ServiceWatcher>(
new ServiceWatcherProxy(this, service_type, callback));
}
scoped_ptr<ServiceResolver> ServiceDiscoveryClientMdns::CreateServiceResolver(
const std::string& service_name,
const ServiceResolver::ResolveCompleteCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
return scoped_ptr<ServiceResolver>(
new ServiceResolverProxy(this, service_name, callback));
}
scoped_ptr<LocalDomainResolver>
ServiceDiscoveryClientMdns::CreateLocalDomainResolver(
const std::string& domain,
net::AddressFamily address_family,
const LocalDomainResolver::IPAddressCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
return scoped_ptr<LocalDomainResolver>(
new LocalDomainResolverProxy(this, domain, address_family, callback));
}
ServiceDiscoveryClientMdns::~ServiceDiscoveryClientMdns() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
DestroyMdns();
}
void ServiceDiscoveryClientMdns::OnNetworkChanged(
net::NetworkChangeNotifier::ConnectionType type) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Only network changes resets counter.
restart_attempts_ = 0;
ScheduleStartNewClient();
}
void ServiceDiscoveryClientMdns::ScheduleStartNewClient() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
OnBeforeMdnsDestroy();
if (restart_attempts_ < kMaxRestartAttempts) {
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&ServiceDiscoveryClientMdns::StartNewClient,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(
kRestartDelayOnNetworkChangeSeconds * (1 << restart_attempts_)));
} else {
ReportSuccess();
}
}
void ServiceDiscoveryClientMdns::StartNewClient() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
++restart_attempts_;
DestroyMdns();
mdns_.reset(net::MDnsClient::CreateDefault().release());
client_.reset(new ServiceDiscoveryClientImpl(mdns_.get()));
BrowserThread::PostTaskAndReplyWithResult(
BrowserThread::FILE,
FROM_HERE,
base::Bind(&net::GetMDnsInterfacesToBind),
base::Bind(&ServiceDiscoveryClientMdns::OnInterfaceListReady,
weak_ptr_factory_.GetWeakPtr()));
}
void ServiceDiscoveryClientMdns::OnInterfaceListReady(
const net::InterfaceIndexFamilyList& interfaces) {
mdns_runner_->PostTask(
FROM_HERE,
base::Bind(&InitMdns,
base::Bind(&ServiceDiscoveryClientMdns::OnMdnsInitialized,
weak_ptr_factory_.GetWeakPtr()),
interfaces,
base::Unretained(mdns_.get())));
}
void ServiceDiscoveryClientMdns::OnMdnsInitialized(bool success) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!success) {
ScheduleStartNewClient();
return;
}
ReportSuccess();
// Initialization is done, no need to delay tasks.
need_dalay_mdns_tasks_ = false;
FOR_EACH_OBSERVER(Proxy, proxies_, OnNewMdnsReady());
}
void ServiceDiscoveryClientMdns::ReportSuccess() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
UMA_HISTOGRAM_COUNTS_100("LocalDiscovery.ClientRestartAttempts",
restart_attempts_);
}
void ServiceDiscoveryClientMdns::OnBeforeMdnsDestroy() {
need_dalay_mdns_tasks_ = true;
weak_ptr_factory_.InvalidateWeakPtrs();
FOR_EACH_OBSERVER(Proxy, proxies_, 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