diff options
author | szym@chromium.org <szym@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-18 04:41:21 +0000 |
---|---|---|
committer | szym@chromium.org <szym@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-18 04:41:21 +0000 |
commit | d84316a989c25e791b13da60635bcfdc2c294d56 (patch) | |
tree | 672581b80714160b074c509fa61c6950c0368701 /net | |
parent | c4dc7efd2c4aa9a7f8580e93d5ce4b0f70652062 (diff) | |
download | chromium_src-d84316a989c25e791b13da60635bcfdc2c294d56.zip chromium_src-d84316a989c25e791b13da60635bcfdc2c294d56.tar.gz chromium_src-d84316a989c25e791b13da60635bcfdc2c294d56.tar.bz2 |
DnsConfigService and a posix implementation
Contributed by: Szymon Jakubczak <szym@chromium.org>
BUG=90881
TEST=./net_unittests --gtest_filter="DnsConfigServiceTest*"
Review URL: http://codereview.chromium.org/7518028
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@97282 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/dns/dns_config_service.cc | 32 | ||||
-rw-r--r-- | net/dns/dns_config_service.h | 91 | ||||
-rw-r--r-- | net/dns/dns_config_service_posix.cc | 277 | ||||
-rw-r--r-- | net/dns/dns_config_service_posix.h | 115 | ||||
-rw-r--r-- | net/dns/dns_config_service_posix_unittest.cc | 472 | ||||
-rw-r--r-- | net/net.gyp | 6 |
6 files changed, 993 insertions, 0 deletions
diff --git a/net/dns/dns_config_service.cc b/net/dns/dns_config_service.cc new file mode 100644 index 0000000..f32f293 --- /dev/null +++ b/net/dns/dns_config_service.cc @@ -0,0 +1,32 @@ +// Copyright (c) 2011 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 "net/dns/dns_config_service.h" + +#include "net/base/ip_endpoint.h" + +namespace net { + +// Default values are taken from glibc resolv.h. +DnsConfig::DnsConfig() + : ndots(1), + timeout(base::TimeDelta::FromSeconds(5)), + attempts(2), + rotate(false), + edns0(false) {} + +DnsConfig::~DnsConfig() {} + +bool DnsConfig::Equals(const DnsConfig& d) const { + return (nameservers == d.nameservers) && + (search == d.search) && + (ndots == d.ndots) && + (timeout == d.timeout) && + (attempts == d.attempts) && + (rotate == d.rotate) && + (edns0 == d.edns0); +} + +} // namespace net + diff --git a/net/dns/dns_config_service.h b/net/dns/dns_config_service.h new file mode 100644 index 0000000..cd4d84c --- /dev/null +++ b/net/dns/dns_config_service.h @@ -0,0 +1,91 @@ +// Copyright (c) 2011 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 NET_DNS_DNS_CONFIG_SERVICE_H_ +#define NET_DNS_DNS_CONFIG_SERVICE_H_ +#pragma once + +#include <list> +#include <map> +#include <string> +#include <vector> + +#include "base/time.h" +#include "net/base/net_export.h" + +namespace net { + +class IPEndPoint; + +// DnsConfig stores configuration of the system resolver. +struct NET_EXPORT_PRIVATE DnsConfig { + DnsConfig(); + virtual ~DnsConfig(); + + bool Equals(const DnsConfig& d) const; + + bool Valid() const { + return !nameservers.empty(); + } + + // List of name server addresses. + std::vector<IPEndPoint> nameservers; + // Suffix search list; used on first lookup when number of dots in given name + // is less than |ndots|. + std::vector<std::string> search; + + // Resolver options; see man resolv.conf. + // TODO(szym): use |ndots| and |search| to determine the sequence of FQDNs + // to query given a specific name. + + // Minimum number of dots before global resolution precedes |search|. + int ndots; + // Time between retransmissions, see res_state.retrans. + base::TimeDelta timeout; + // Maximum number of retries, see res_state.retry. + int attempts; + // Round robin entries in |nameservers| for subsequent requests. + bool rotate; + // Enable EDNS0 extensions. + bool edns0; +}; + +// Service for watching when the system DNS settings have changed. +// Depending on the platform, watches files in /etc/ or win registry. +class NET_EXPORT_PRIVATE DnsConfigService { + public: + // Callback interface for the client. The observer is called on the same + // thread as Watch(). Observer must outlive the service. + class Observer { + public: + virtual ~Observer() {} + + // Called only when |dns_config| is different from the last check. + virtual void OnConfigChanged(const DnsConfig& dns_config) = 0; + }; + + // Creates the platform-specific DnsConfigService. + static DnsConfigService* CreateSystemService(); + + DnsConfigService() {} + virtual ~DnsConfigService() {} + + // Immediately starts watching system configuration for changes and attempts + // to read the configuration. For some platform implementations, the current + // thread must have an IO loop (for base::files::FilePathWatcher). + virtual void Watch() = 0; + + // If a config is available, |observer| will immediately be called with + // OnConfigChanged. + virtual void AddObserver(Observer* observer) = 0; + virtual void RemoveObserver(Observer* observer) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(DnsConfigService); +}; + +} // namespace net + +#endif // NET_DNS_DNS_CONFIG_SERVICE_H_ + diff --git a/net/dns/dns_config_service_posix.cc b/net/dns/dns_config_service_posix.cc new file mode 100644 index 0000000..d610b1f --- /dev/null +++ b/net/dns/dns_config_service_posix.cc @@ -0,0 +1,277 @@ +// Copyright (c) 2011 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 "net/dns/dns_config_service_posix.h" + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/files/file_path_watcher.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/observer_list.h" +#include "base/scoped_ptr.h" +#include "base/threading/worker_pool.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_util.h" + +#ifndef _PATH_RESCONF // Normally defined in <resolv.h> +#define _PATH_RESCONF "/etc/resolv.conf" +#endif + +namespace net { + +// FilePathWatcher::Delegate is refcounted, so we separate it from the Service +// It also hosts callbacks on the WorkerPool. +class DnsConfigServicePosix::WatcherDelegate + : public base::files::FilePathWatcher::Delegate { + public: + // Takes ownership of |lib|. + WatcherDelegate(DnsConfigServicePosix* service, + DnsConfigServicePosix::ResolverLib* lib) + : service_(service), + resolver_lib_(lib), + message_loop_(base::MessageLoopProxy::current()), + reading_(false), + read_pending_(false) {} + + void Cancel() { + DCHECK(message_loop_->BelongsToCurrentThread()); + service_ = NULL; + } + + void RescheduleWatch() { + DCHECK(message_loop_->BelongsToCurrentThread()); + // Retry Watch in 100ms or so. + message_loop_->PostDelayedTask( + FROM_HERE, base::Bind(&WatcherDelegate::StartWatch, this), 100); + } + + // FilePathWatcher::Delegate interface + virtual void OnFilePathChanged(const FilePath& path) OVERRIDE { + DCHECK(message_loop_->BelongsToCurrentThread()); + if (!service_) + return; + ScheduleRead(); + } + + virtual void OnFilePathError(const FilePath& path) OVERRIDE { + DCHECK(message_loop_->BelongsToCurrentThread()); + StartWatch(); + } + + private: + virtual ~WatcherDelegate() {} + + // Unless already scheduled, post DoRead to WorkerPool. + void ScheduleRead() { + if (reading_) { + // Mark that we need to re-read after DoRead posts results. + read_pending_ = true; + } else { + if (!base::WorkerPool::PostTask(FROM_HERE, base::Bind( + &WatcherDelegate::DoRead, this), false)) { + // See worker_pool_posix.cc. + NOTREACHED() << "WorkerPool::PostTask is not expected to fail on posix"; + } + reading_ = true; + read_pending_ = false; + } + } + + // Reads DnsConfig and posts OnResultAvailable to |message_loop_|. + // Must be called on the worker thread. + void DoRead() { + DnsConfig config; + struct __res_state res; + bool success = false; + if (resolver_lib_->ninit(&res) == 0) { + success = ConvertResToConfig(res, &config); + resolver_lib_->nclose(&res); + } + // If this fails, the loop is gone, so there is no point retrying. + message_loop_->PostTask(FROM_HERE, base::Bind( + &WatcherDelegate::OnResultAvailable, this, config, success)); + } + + // Communicates result to the service. Must be called on the the same thread + // that constructed WatcherDelegate. + void OnResultAvailable(const DnsConfig &config, bool success) { + DCHECK(message_loop_->BelongsToCurrentThread()); + if (!service_) + return; + reading_ = false; + if (read_pending_) { + // Discard this result and re-schedule. + ScheduleRead(); + return; + } + if (!success) { + VLOG(1) << "Failed to read DnsConfig"; + } else { + service_->OnConfigRead(config); + } + } + + // To avoid refcounting the service, use this method in tasks/callbacks. + void StartWatch() { + if (!service_) + return; + service_->StartWatch(); + } + + DnsConfigServicePosix* service_; + scoped_ptr<DnsConfigServicePosix::ResolverLib> resolver_lib_; + // Message loop for the thread on which Watch is called (of TYPE_IO). + scoped_refptr<base::MessageLoopProxy> message_loop_; + // True after DoRead before OnResultsAvailable. + bool reading_; + // True after OnFilePathChanged fires while |reading_| is true. + bool read_pending_; +}; + +DnsConfigServicePosix::DnsConfigServicePosix() + : have_config_(false), + resolver_lib_(new ResolverLib()), + watcher_factory_(new FilePathWatcherFactory()) { +} + +DnsConfigServicePosix::~DnsConfigServicePosix() { + DCHECK(CalledOnValidThread()); + if (watcher_delegate_.get()) + watcher_delegate_->Cancel(); +} + +void DnsConfigServicePosix::AddObserver(Observer* observer) { + DCHECK(CalledOnValidThread()); + observers_.AddObserver(observer); + if (have_config_) { + observer->OnConfigChanged(dns_config_); + } +} + +void DnsConfigServicePosix::RemoveObserver(Observer* observer) { + DCHECK(CalledOnValidThread()); + observers_.RemoveObserver(observer); +} + +void DnsConfigServicePosix::Watch() { + DCHECK(CalledOnValidThread()); + DCHECK(!watcher_delegate_.get()); + DCHECK(!resolv_file_watcher_.get()); + DCHECK(resolver_lib_.get()); + DCHECK(watcher_factory_.get()); + + watcher_delegate_ = new WatcherDelegate(this, resolver_lib_.release()); + StartWatch(); +} + +void DnsConfigServicePosix::OnConfigRead(const DnsConfig& config) { + DCHECK(CalledOnValidThread()); + if (!config.Equals(dns_config_)) { + dns_config_ = config; + have_config_ = true; + FOR_EACH_OBSERVER(Observer, observers_, OnConfigChanged(config)); + } +} + +void DnsConfigServicePosix::StartWatch() { + DCHECK(CalledOnValidThread()); + DCHECK(watcher_delegate_.get()); + + FilePath path(FILE_PATH_LITERAL(_PATH_RESCONF)); + + // FilePathWatcher allows only one Watch per lifetime, so we need a new one. + resolv_file_watcher_.reset(watcher_factory_->CreateFilePathWatcher()); + if (resolv_file_watcher_->Watch(path, watcher_delegate_.get())) { + // Make the initial read after watch is installed. + watcher_delegate_->OnFilePathChanged(path); + } else { + VLOG(1) << "Watch failed, scheduling restart"; + watcher_delegate_->RescheduleWatch(); + } +} + +int DnsConfigServicePosix::ResolverLib::ninit(res_state statp) { + return ::res_ninit(statp); +} + +void DnsConfigServicePosix::ResolverLib::nclose(res_state statp) { + return ::res_nclose(statp); +} + +DnsConfigServicePosix::FilePathWatcherShim::FilePathWatcherShim() + : watcher_(new base::files::FilePathWatcher()) {} +DnsConfigServicePosix::FilePathWatcherShim::~FilePathWatcherShim() {} + +bool DnsConfigServicePosix::FilePathWatcherShim::Watch( + const FilePath& path, + base::files::FilePathWatcher::Delegate* delegate) { + return watcher_->Watch(path, delegate); +} + +DnsConfigServicePosix::FilePathWatcherShim* +DnsConfigServicePosix::FilePathWatcherFactory::CreateFilePathWatcher() { + return new FilePathWatcherShim(); +} + +// static +DnsConfigService* DnsConfigService::CreateSystemService() { + return new DnsConfigServicePosix(); +} + +bool ConvertResToConfig(const struct __res_state& res, DnsConfig* dns_config) { + CHECK(dns_config != NULL); + DCHECK(res.options & RES_INIT); + + dns_config->nameservers.clear(); + +#if OS_LINUX + // Initially, glibc stores IPv6 in _ext.nsaddrs and IPv4 in nsaddr_list. + // Next (res_send.c::__libc_res_nsend), it copies nsaddr_list after nsaddrs. + // If RES_ROTATE is enabled, the list is shifted left after each res_send. + // However, if nsaddr_list changes, it will refill nsaddr_list (IPv4) but + // leave the IPv6 entries in nsaddr in the same (shifted) order. + + // Put IPv6 addresses ahead of IPv4. + for (int i = 0; i < res._u._ext.nscount6; ++i) { + IPEndPoint ipe; + if (ipe.FromSockAddr( + reinterpret_cast<const struct sockaddr*>(res._u._ext.nsaddrs[i]), + sizeof *res._u._ext.nsaddrs[i])) { + dns_config->nameservers.push_back(ipe); + } else { + return false; + } + } +#endif + + for (int i = 0; i < res.nscount; ++i) { + IPEndPoint ipe; + if (ipe.FromSockAddr( + reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]), + sizeof res.nsaddr_list[i])) { + dns_config->nameservers.push_back(ipe); + } else { + return false; + } + } + + dns_config->search.clear(); + for (int i = 0; (i < MAXDNSRCH) && res.dnsrch[i]; ++i) { + dns_config->search.push_back(std::string(res.dnsrch[i])); + } + + dns_config->ndots = res.ndots; + dns_config->timeout = base::TimeDelta::FromSeconds(res.retrans); + dns_config->attempts = res.retry; + dns_config->rotate = res.options & RES_ROTATE; + dns_config->edns0 = res.options & RES_USE_EDNS0; + + return true; +} + +} // namespace net + diff --git a/net/dns/dns_config_service_posix.h b/net/dns/dns_config_service_posix.h new file mode 100644 index 0000000..10c04ef --- /dev/null +++ b/net/dns/dns_config_service_posix.h @@ -0,0 +1,115 @@ +// Copyright (c) 2011 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 NET_DNS_DNS_CONFIG_SERVICE_POSIX_H_ +#define NET_DNS_DNS_CONFIG_SERVICE_POSIX_H_ +#pragma once + +#include <resolv.h> + +#include "base/compiler_specific.h" +#include "base/files/file_path_watcher.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "base/scoped_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "net/base/net_export.h" +#include "net/dns/dns_config_service.h" + +namespace net { + +class NET_EXPORT_PRIVATE DnsConfigServicePosix + : public NON_EXPORTED_BASE(DnsConfigService), + public NON_EXPORTED_BASE(base::NonThreadSafe) { + public: + class ResolverLib; + class FilePathWatcherShim; + class FilePathWatcherFactory; + class WatcherDelegate; + + DnsConfigServicePosix(); + virtual ~DnsConfigServicePosix(); + + virtual void AddObserver(Observer* observer) OVERRIDE; + virtual void RemoveObserver(Observer* observer) OVERRIDE; + virtual void Watch() OVERRIDE; + + // Takes ownership of |lib|. Must be set before Watch. + void set_resolver_lib(ResolverLib* lib) { + DCHECK(!watcher_delegate_.get()); + resolver_lib_.reset(lib); + } + + // Takes ownership of |factory|. Must be set before Watch. + void set_watcher_factory(FilePathWatcherFactory* factory) { + DCHECK(!watcher_delegate_.get()); + watcher_factory_.reset(factory); + } + + private: + void OnConfigRead(const DnsConfig& config); + + // Configure a FilePathWatcher and install watcher_delegate_. Executed each + // time FilePathWatcher fails. + void StartWatch(); + + DnsConfig dns_config_; + // True after first OnConfigChanged, that is, dns_config_ is valid. + bool have_config_; + + scoped_ptr<ResolverLib> resolver_lib_; + scoped_ptr<FilePathWatcherFactory> watcher_factory_; + scoped_ptr<FilePathWatcherShim> resolv_file_watcher_; + scoped_refptr<WatcherDelegate> watcher_delegate_; + ObserverList<Observer> observers_; + + DISALLOW_COPY_AND_ASSIGN(DnsConfigServicePosix); +}; + + +// Allows mocking res_ninit. +class NET_EXPORT_PRIVATE DnsConfigServicePosix::ResolverLib + : public NON_EXPORTED_BASE(base::NonThreadSafe) { + public: + ResolverLib() {} + virtual ~ResolverLib() {} + virtual int ninit(res_state statp); + virtual void nclose(res_state statp); + private: + DISALLOW_COPY_AND_ASSIGN(ResolverLib); +}; + +// Allows mocking FilePathWatcher +class NET_EXPORT_PRIVATE DnsConfigServicePosix::FilePathWatcherShim + : public NON_EXPORTED_BASE(base::NonThreadSafe) { + public: + FilePathWatcherShim(); + virtual ~FilePathWatcherShim(); + + virtual bool Watch( + const FilePath& path, + base::files::FilePathWatcher::Delegate* delegate) WARN_UNUSED_RESULT; + private: + scoped_ptr<base::files::FilePathWatcher> watcher_; + DISALLOW_COPY_AND_ASSIGN(FilePathWatcherShim); +}; + +class NET_EXPORT_PRIVATE DnsConfigServicePosix::FilePathWatcherFactory + : public NON_EXPORTED_BASE(base::NonThreadSafe) { + public: + FilePathWatcherFactory() {} + virtual ~FilePathWatcherFactory() {} + virtual FilePathWatcherShim* CreateFilePathWatcher(); + private: + DISALLOW_COPY_AND_ASSIGN(FilePathWatcherFactory); +}; + +// Fills in |dns_config| from |res|. Exposed for tests. +bool NET_EXPORT_PRIVATE ConvertResToConfig(const struct __res_state& res, + DnsConfig* dns_config); + +} // namespace net + +#endif // NET_DNS_DNS_CONFIG_SERVICE_POSIX_H_ + diff --git a/net/dns/dns_config_service_posix_unittest.cc b/net/dns/dns_config_service_posix_unittest.cc new file mode 100644 index 0000000..9c81e14 --- /dev/null +++ b/net/dns/dns_config_service_posix_unittest.cc @@ -0,0 +1,472 @@ +// Copyright (c) 2011 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 <arpa/inet.h> +#include <resolv.h> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "net/base/ip_endpoint.h" +#include "net/dns/dns_config_service_posix.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +void CompareConfig(const struct __res_state &res, const DnsConfig& config) { + EXPECT_EQ(config.ndots, static_cast<int>(res.ndots)); + EXPECT_EQ(config.edns0, (res.options & RES_USE_EDNS0) != 0); + EXPECT_EQ(config.rotate, (res.options & RES_ROTATE) != 0); + EXPECT_EQ(config.timeout.InSeconds(), res.retrans); + EXPECT_EQ(config.attempts, res.retry); + + // Compare nameservers. IPv6 precede IPv4. +#if OS_LINUX + size_t nscount6 = res._u._ext.nscount6; +#else + size_t nscount6 = 0; +#endif + size_t nscount4 = res.nscount; + ASSERT_EQ(config.nameservers.size(), nscount6 + nscount4); +#if OS_LINUX + for (size_t i = 0; i < nscount6; ++i) { + IPEndPoint ipe; + EXPECT_TRUE(ipe.FromSockAddr( + reinterpret_cast<const struct sockaddr*>(res._u._ext.nsaddrs[i]), + sizeof *res._u._ext.nsaddrs[i])); + EXPECT_EQ(config.nameservers[i], ipe); + } +#endif + for (size_t i = 0; i < nscount4; ++i) { + IPEndPoint ipe; + EXPECT_TRUE(ipe.FromSockAddr( + reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]), + sizeof res.nsaddr_list[i])); + EXPECT_EQ(config.nameservers[nscount6 + i], ipe); + } + + ASSERT_TRUE(config.search.size() <= MAXDNSRCH); + EXPECT_TRUE(res.dnsrch[config.search.size()] == NULL); + for (size_t i = 0; i < config.search.size(); ++i) { + EXPECT_EQ(config.search[i], res.dnsrch[i]); + } +} + +// Fills in |res| with sane configuration. Change |generation| to add diversity. +void InitializeResState(res_state res, int generation) { + memset(res, 0, sizeof(*res)); + res->options = RES_INIT | RES_ROTATE; + res->ndots = 2; + res->retrans = 8; + res->retry = 7; + + const char kDnsrch[] = "chromium.org" "\0" "example.com"; + memcpy(res->defdname, kDnsrch, sizeof(kDnsrch)); + res->dnsrch[0] = res->defdname; + res->dnsrch[1] = res->defdname + sizeof("chromium.org"); + + const char* ip4addr[3] = { + "8.8.8.8", + "192.168.1.1", + "63.1.2.4", + }; + + for (int i = 0; i < 3; ++i) { + struct sockaddr_in sa; + sa.sin_family = AF_INET; + sa.sin_port = htons(NS_DEFAULTPORT + i - generation); + inet_pton(AF_INET, ip4addr[i], &sa.sin_addr); + res->nsaddr_list[i] = sa; + } + res->nscount = 3; + +#if OS_LINUX + const char* ip6addr[2] = { + "2001:db8:0::42", + "::FFFF:129.144.52.38", + }; + + for (int i = 0; i < 2; ++i) { + // Must use malloc to mimick res_ninit. + struct sockaddr_in6 *sa6; + sa6 = (struct sockaddr_in6 *)malloc(sizeof(*sa6)); + sa6->sin6_family = AF_INET6; + sa6->sin6_port = htons(NS_DEFAULTPORT - i); + inet_pton(AF_INET6, ip6addr[i], &sa6->sin6_addr); + res->_u._ext.nsaddrs[i] = sa6; + } + res->_u._ext.nscount6 = 2; +#endif +} + +void CloseResState(res_state res) { +#if OS_LINUX + for (int i = 0; i < res->_u._ext.nscount6; ++i) { + ASSERT_TRUE(res->_u._ext.nsaddrs[i] != NULL); + free(res->_u._ext.nsaddrs[i]); + } +#endif +} + +class DnsConfigServiceTest : public testing::Test, + public DnsConfigService::Observer { + public: + // Mocks + + // DnsConfigService owns the instances of ResolverLib and + // FilePathWatcherFactory that it gets, so use simple proxies to call + // DnsConfigServiceTest. + + // ResolverLib is owned by WatcherDelegate which is posted to WorkerPool so + // it must be canceled before the test is over. + class MockResolverLib : public DnsConfigServicePosix::ResolverLib { + public: + explicit MockResolverLib(DnsConfigServiceTest *test) : test_(test) {} + virtual ~MockResolverLib() { + base::AutoLock lock(lock_); + if (test_) { + EXPECT_TRUE(test_->IsComplete()); + } + } + virtual int ninit(res_state res) OVERRIDE { + base::AutoLock lock(lock_); + if (test_) + return test_->OnNinit(res); + else + return -1; + } + virtual void nclose(res_state res) OVERRIDE { + CloseResState(res); + } + void Cancel() { + base::AutoLock lock(lock_); + test_ = NULL; + } + private: + base::Lock lock_; + DnsConfigServiceTest *test_; + }; + + class MockFilePathWatcherShim + : public DnsConfigServicePosix::FilePathWatcherShim { + public: + typedef base::files::FilePathWatcher::Delegate Delegate; + + explicit MockFilePathWatcherShim(DnsConfigServiceTest* t) : test_(t) {} + virtual ~MockFilePathWatcherShim() { + test_->OnShimDestroyed(this); + } + + // Enforce one-Watch-per-lifetime as the original FilePathWatcher + virtual bool Watch(const FilePath& path, + Delegate* delegate) OVERRIDE { + EXPECT_TRUE(path_.empty()) << "Only one-Watch-per-lifetime allowed."; + EXPECT_TRUE(!delegate_.get()); + path_ = path; + delegate_ = delegate; + return test_->OnWatch(); + } + + void PathChanged() { + delegate_->OnFilePathChanged(path_); + } + + void PathError() { + delegate_->OnFilePathError(path_); + } + + private: + FilePath path_; + scoped_refptr<Delegate> delegate_; + DnsConfigServiceTest* test_; + }; + + class MockFilePathWatcherFactory + : public DnsConfigServicePosix::FilePathWatcherFactory { + public: + explicit MockFilePathWatcherFactory(DnsConfigServiceTest* t) : test(t) {} + virtual ~MockFilePathWatcherFactory() { + EXPECT_TRUE(test->IsComplete()); + } + virtual DnsConfigServicePosix::FilePathWatcherShim* + CreateFilePathWatcher() OVERRIDE { + return test->CreateFilePathWatcher(); + } + DnsConfigServiceTest* test; + }; + + // Helpers for mocks. + + DnsConfigServicePosix::FilePathWatcherShim* CreateFilePathWatcher() { + watcher_shim_ = new MockFilePathWatcherShim(this); + return watcher_shim_; + } + + void OnShimDestroyed(MockFilePathWatcherShim* destroyed_shim) { + // Precaution to avoid segfault. + if (watcher_shim_ == destroyed_shim) + watcher_shim_ = NULL; + } + + // On each event, post QuitTask to allow use of MessageLoop::Run() to + // synchronize the threads. + + bool OnWatch() { + EXPECT_TRUE(message_loop_ == MessageLoop::current()); + watch_called_ = true; + BreakNow("OnWatch"); + return !fail_on_watch_; + } + + int OnNinit(res_state res) { + { // Check that res_ninit is executed serially. + base::AutoLock lock(ninit_lock_); + EXPECT_FALSE(ninit_running_) << "res_ninit is not called serially!"; + ninit_running_ = true; + } + BreakNow("OnNinit"); + ninit_allowed_.Wait(); + // Calling from another thread is a bit dirty, but it's protected. + int rv = OnNinitNonThreadSafe(res); + // This lock might be destroyed after ninit_called_ is signalled. + { + base::AutoLock lock(ninit_lock_); + ninit_running_ = false; + } + ninit_called_.Signal(); + return rv; + } + + virtual void OnConfigChanged(const DnsConfig& new_config) OVERRIDE { + EXPECT_TRUE(message_loop_ == MessageLoop::current()); + CompareConfig(res_, new_config); + EXPECT_FALSE(new_config.Equals(last_config_)) << + "Config must be different from last call."; + last_config_ = new_config; + got_config_ = true; + BreakNow("OnConfigChanged"); + } + + bool IsComplete() { + return complete_; + } + + protected: + friend class BreakTask; + class BreakTask : public Task { + public: + BreakTask(DnsConfigServiceTest* test, std::string breakpoint) + : test_(test), breakpoint_(breakpoint) {} + virtual ~BreakTask() {} + virtual void Run() OVERRIDE { + test_->breakpoint_ = breakpoint_; + MessageLoop::current()->QuitNow(); + } + private: + DnsConfigServiceTest* test_; + std::string breakpoint_; + }; + + void BreakNow(std::string b) { + message_loop_->PostTask(FROM_HERE, new BreakTask(this, b)); + } + + void RunUntilBreak(std::string b) { + message_loop_->Run(); + ASSERT_EQ(breakpoint_, b); + } + + DnsConfigServiceTest() + : res_lib_(new MockResolverLib(this)), + watcher_shim_(NULL), + res_generation_(1), + watch_called_(false), + got_config_(false), + fail_on_watch_(false), + fail_on_ninit_(false), + complete_(false), + ninit_allowed_(false, false), + ninit_called_(false, false), + ninit_running_(false) { + } + + // This is on WorkerPool, but protected by ninit_allowed_. + int OnNinitNonThreadSafe(res_state res) { + if (fail_on_ninit_) + return -1; + InitializeResState(res, res_generation_); + // Store a (deep) copy in the fixture to later verify correctness. + CloseResState(&res_); + InitializeResState(&res_, res_generation_); + return 0; + } + + // Helpers for tests. + + // Lets OnNinit run OnNinitNonThreadSafe and waits for it to complete. + // Might get OnConfigChanged scheduled on the loop but we have no certainty. + void WaitForNinit() { + RunUntilBreak("OnNinit"); + ninit_allowed_.Signal(); + ninit_called_.Wait(); + } + + // test::Test methods + virtual void SetUp() OVERRIDE { + message_loop_ = MessageLoop::current(); + service_.reset(new DnsConfigServicePosix()); + service_->set_resolver_lib(res_lib_); + service_->set_watcher_factory(new MockFilePathWatcherFactory(this)); + memset(&res_, 0, sizeof(res_)); + } + + virtual void TearDown() OVERRIDE { + // res_lib_ could outlive the test, so make sure it doesn't call it. + res_lib_->Cancel(); + CloseResState(&res_); + // Allow service_ to clean up ResolverLib and FilePathWatcherFactory. + complete_ = true; + } + + MockResolverLib* res_lib_; + MockFilePathWatcherShim* watcher_shim_; + // Adds variety to the content of res_state. + int res_generation_; + struct __res_state res_; + DnsConfig last_config_; + + bool watch_called_; + bool got_config_; + bool fail_on_watch_; + bool fail_on_ninit_; + bool complete_; + + // Ninit is called on WorkerPool so we need to synchronize with it. + base::WaitableEvent ninit_allowed_; + base::WaitableEvent ninit_called_; + + // Protected by ninit_lock_. Used to verify that Ninit calls are serialized. + bool ninit_running_; + base::Lock ninit_lock_; + + // Loop for this thread. + MessageLoop* message_loop_; + + // Service under test. + scoped_ptr<DnsConfigServicePosix> service_; + + std::string breakpoint_; +}; + +TEST(DnsConfigTest, ResolverConfigConvertAndEquals) { + struct __res_state res[2]; + DnsConfig config[2]; + for (int i = 0; i < 2; ++i) { + InitializeResState(&res[i], i); + ASSERT_TRUE(ConvertResToConfig(res[i], &config[i])); + } + for (int i = 0; i < 2; ++i) { + CompareConfig(res[i], config[i]); + CloseResState(&res[i]); + } + EXPECT_TRUE(config[0].Equals(config[0])); + EXPECT_FALSE(config[0].Equals(config[1])); + EXPECT_FALSE(config[1].Equals(config[0])); +} + +TEST_F(DnsConfigServiceTest, FilePathWatcherFailures) { + // For these tests, disable ninit. + res_lib_->Cancel(); + + fail_on_watch_ = true; + service_->Watch(); + RunUntilBreak("OnWatch"); + EXPECT_TRUE(watch_called_) << "Must call FilePathWatcher::Watch()."; + + fail_on_watch_ = false; + watch_called_ = false; + RunUntilBreak("OnWatch"); // Due to backoff this will take 100ms. + EXPECT_TRUE(watch_called_) << + "Must restart on FilePathWatcher::Watch() failure."; + + watch_called_ = false; + ASSERT_TRUE(watcher_shim_); + watcher_shim_->PathError(); + RunUntilBreak("OnWatch"); + EXPECT_TRUE(watch_called_) << + "Must restart on FilePathWatcher::Delegate::OnFilePathError()."; + + // Worker thread could still be posting OnResultAvailable to the message loop +} + +TEST_F(DnsConfigServiceTest, NotifyOnValidAndDistinctConfig) { + service_->AddObserver(this); + service_->Watch(); + RunUntilBreak("OnWatch"); + fail_on_ninit_ = true; + WaitForNinit(); + + // If OnNinit posts OnResultAvailable before the next call, then this test + // verifies that failure on ninit should not cause OnConfigChanged. + // Otherwise, this only verifies that ninit calls are serialized. + + fail_on_ninit_ = false; + ASSERT_TRUE(watcher_shim_); + watcher_shim_->PathChanged(); + WaitForNinit(); + + RunUntilBreak("OnConfigChanged"); + EXPECT_TRUE(got_config_); + + message_loop_->AssertIdle(); + + got_config_ = false; + // Forget about the config to test if we get it again on AddObserver. + last_config_ = DnsConfig(); + service_->RemoveObserver(this); + service_->AddObserver(this); + RunUntilBreak("OnConfigChanged"); + EXPECT_TRUE(got_config_) << "Did not get config after AddObserver."; + + // Simulate spurious FilePathChanged. + ASSERT_TRUE(watcher_shim_); + watcher_shim_->PathChanged(); + WaitForNinit(); + + // OnConfigChanged will catch that the config did not actually change. + + got_config_ = false; + ++res_generation_; + ASSERT_TRUE(watcher_shim_); + watcher_shim_->PathChanged(); + WaitForNinit(); + RunUntilBreak("OnConfigChanged"); + EXPECT_TRUE(got_config_) << "Did not get config after change"; + + message_loop_->AssertIdle(); + + // Schedule two calls. OnNinit checks if it is called serially. + ++res_generation_; + ASSERT_TRUE(watcher_shim_); + watcher_shim_->PathChanged(); + // ninit is blocked, so this will have to induce read_pending + watcher_shim_->PathChanged(); + WaitForNinit(); + WaitForNinit(); + RunUntilBreak("OnConfigChanged"); + EXPECT_TRUE(got_config_) << "Did not get config after change"; + + // We should be done with all tasks. + message_loop_->AssertIdle(); +} + +} // namespace + +} // namespace net + diff --git a/net/net.gyp b/net/net.gyp index 8010016..40bade8 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -290,6 +290,10 @@ 'disk_cache/trace.h', 'dns/async_host_resolver.cc', 'dns/async_host_resolver.h', + 'dns/dns_config_service.cc', + 'dns/dns_config_service.h', + 'dns/dns_config_service_posix.cc', + 'dns/dns_config_service_posix.h', 'dns/dns_query.cc', 'dns/dns_query.h', 'dns/dns_response.cc', @@ -941,6 +945,7 @@ 'disk_cache/mapped_file_unittest.cc', 'disk_cache/storage_block_unittest.cc', 'dns/async_host_resolver_unittest.cc', + 'dns/dns_config_service_posix_unittest.cc', 'dns/dns_query_unittest.cc', 'dns/dns_response_unittest.cc', 'dns/dns_transaction_unittest.cc', @@ -1108,6 +1113,7 @@ ], [ 'OS == "win"', { 'sources!': [ + 'dns/dns_config_service_posix_unittest.cc', 'http/http_auth_gssapi_posix_unittest.cc', ], # This is needed to trigger the dll copy step on windows. |