summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorszym@chromium.org <szym@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-08-18 04:41:21 +0000
committerszym@chromium.org <szym@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-08-18 04:41:21 +0000
commitd84316a989c25e791b13da60635bcfdc2c294d56 (patch)
tree672581b80714160b074c509fa61c6950c0368701 /net
parentc4dc7efd2c4aa9a7f8580e93d5ce4b0f70652062 (diff)
downloadchromium_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.cc32
-rw-r--r--net/dns/dns_config_service.h91
-rw-r--r--net/dns/dns_config_service_posix.cc277
-rw-r--r--net/dns/dns_config_service_posix.h115
-rw-r--r--net/dns/dns_config_service_posix_unittest.cc472
-rw-r--r--net/net.gyp6
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.