diff options
author | szym@chromium.org <szym@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-02-07 23:21:57 +0000 |
---|---|---|
committer | szym@chromium.org <szym@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-02-07 23:21:57 +0000 |
commit | fea484e58d81b67f8d52ee456b15cba75a555c8a (patch) | |
tree | 2dd96b20d40c8cfc1bdf27854eb3cd4f73543108 /net | |
parent | b5827790ef97a92a783414f216e83bd98fcf68de (diff) | |
download | chromium_src-fea484e58d81b67f8d52ee456b15cba75a555c8a.zip chromium_src-fea484e58d81b67f8d52ee456b15cba75a555c8a.tar.gz chromium_src-fea484e58d81b67f8d52ee456b15cba75a555c8a.tar.bz2 |
[net/dns] Suffix search list for DnsConfigService and related features.
Implements domain name devolution, search list policy and AppendToMultiLabelName. Also fixes behavior for single-label names with empty search list.
BUG=99510,109902,109949
TEST=./net_unittests --gtest_filter=DnsConfigServiceWin*
Review URL: http://codereview.chromium.org/9232059
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@120851 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/dns/dns_config_service.cc | 24 | ||||
-rw-r--r-- | net/dns/dns_config_service.h | 6 | ||||
-rw-r--r-- | net/dns/dns_config_service_posix.cc | 9 | ||||
-rw-r--r-- | net/dns/dns_config_service_posix.h | 6 | ||||
-rw-r--r-- | net/dns/dns_config_service_posix_unittest.cc | 10 | ||||
-rw-r--r-- | net/dns/dns_config_service_win.cc | 440 | ||||
-rw-r--r-- | net/dns/dns_config_service_win.h | 71 | ||||
-rw-r--r-- | net/dns/dns_config_service_win_unittest.cc | 294 | ||||
-rw-r--r-- | net/dns/dns_transaction.cc | 26 | ||||
-rw-r--r-- | net/dns/dns_transaction_unittest.cc | 165 |
10 files changed, 809 insertions, 242 deletions
diff --git a/net/dns/dns_config_service.cc b/net/dns/dns_config_service.cc index dcf58bd..d64b6735 100644 --- a/net/dns/dns_config_service.cc +++ b/net/dns/dns_config_service.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -11,17 +11,19 @@ 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) {} + : append_to_multi_label_name(true), + ndots(1), + timeout(base::TimeDelta::FromSeconds(5)), + attempts(2), + rotate(false), + edns0(false) {} DnsConfig::~DnsConfig() {} bool DnsConfig::EqualsIgnoreHosts(const DnsConfig& d) const { return (nameservers == d.nameservers) && (search == d.search) && + (append_to_multi_label_name == d.append_to_multi_label_name) && (ndots == d.ndots) && (timeout == d.timeout) && (attempts == d.attempts) && @@ -34,8 +36,8 @@ bool DnsConfig::Equals(const DnsConfig& d) const { } DnsConfigService::DnsConfigService() - : have_config_(false), - have_hosts_(false) {} + : have_config_(false), + have_hosts_(false) {} DnsConfigService::~DnsConfigService() {} @@ -77,9 +79,9 @@ void DnsConfigService::OnHostsRead(const DnsHosts& hosts) { } DnsHostsReader::DnsHostsReader(const FilePath& path, DnsConfigService* service) - : path_(path), - service_(service), - success_(false) { + : path_(path), + service_(service), + success_(false) { DCHECK(service); } diff --git a/net/dns/dns_config_service.h b/net/dns/dns_config_service.h index 5fdd2b6..b3e151f 100644 --- a/net/dns/dns_config_service.h +++ b/net/dns/dns_config_service.h @@ -39,13 +39,15 @@ struct NET_EXPORT_PRIVATE DnsConfig { std::vector<IPEndPoint> nameservers; // Suffix search list; used on first lookup when number of dots in given name // is less than |ndots|. - // TODO(szym): Filter out duplicate entries from this list. std::vector<std::string> search; DnsHosts hosts; + // AppendToMultiLabelName: is suffix search performed for multi-label names? + // True, except on Windows where it can be configured. + bool append_to_multi_label_name; + // Resolver options; see man resolv.conf. - // TODO(szym): implement DNS Devolution for windows // Minimum number of dots before global resolution precedes |search|. int ndots; diff --git a/net/dns/dns_config_service_posix.cc b/net/dns/dns_config_service_posix.cc index f7833bc..18a69cc 100644 --- a/net/dns/dns_config_service_posix.cc +++ b/net/dns/dns_config_service_posix.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -38,12 +38,12 @@ class DnsConfigServicePosix::ConfigReader : public SerialWorker { // Note: res_ninit in glibc always returns 0 and sets RES_INIT. // res_init behaves the same way. if ((res_init() == 0) && (_res.options & RES_INIT)) { - success_ = ConvertResToConfig(_res, &dns_config_); + success_ = ConvertResStateToDnsConfig(_res, &dns_config_); } #else struct __res_state res; if ((res_ninit(&res) == 0) && (res.options & RES_INIT)) { - success_ = ConvertResToConfig(res, &dns_config_); + success_ = ConvertResStateToDnsConfig(res, &dns_config_); } #endif #if defined(OS_MACOSX) @@ -89,7 +89,8 @@ DnsConfigService* DnsConfigService::CreateSystemService() { } #if !defined(OS_ANDROID) -bool ConvertResToConfig(const struct __res_state& res, DnsConfig* dns_config) { +bool ConvertResStateToDnsConfig(const struct __res_state& res, + DnsConfig* dns_config) { CHECK(dns_config != NULL); DCHECK(res.options & RES_INIT); diff --git a/net/dns/dns_config_service_posix.h b/net/dns/dns_config_service_posix.h index 4fd48d2..fa9599e 100644 --- a/net/dns/dns_config_service_posix.h +++ b/net/dns/dns_config_service_posix.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -36,8 +36,8 @@ class NET_EXPORT_PRIVATE DnsConfigServicePosix }; // Fills in |dns_config| from |res|. Exposed for tests. -bool NET_EXPORT_PRIVATE ConvertResToConfig(const struct __res_state& res, - DnsConfig* dns_config); +bool NET_EXPORT_PRIVATE ConvertResStateToDnsConfig( + const struct __res_state& res, DnsConfig* dns_config); } // namespace net diff --git a/net/dns/dns_config_service_posix_unittest.cc b/net/dns/dns_config_service_posix_unittest.cc index ab9e56d..58c71ac 100644 --- a/net/dns/dns_config_service_posix_unittest.cc +++ b/net/dns/dns_config_service_posix_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -53,7 +53,7 @@ void CompareConfig(const struct __res_state &res, const DnsConfig& config) { } // Fills in |res| with sane configuration. Change |generation| to add diversity. -void InitializeResState(res_state res, int generation) { +void InitializeResState(res_state res, unsigned generation) { memset(res, 0, sizeof(*res)); res->options = RES_INIT | RES_ROTATE; res->ndots = 2; @@ -113,12 +113,12 @@ void CloseResState(res_state res) { TEST(DnsConfigTest, ResolverConfigConvertAndEquals) { struct __res_state res[2]; DnsConfig config[2]; - for (int i = 0; i < 2; ++i) { + for (unsigned i = 0; i < 2; ++i) { EXPECT_FALSE(config[i].IsValid()); InitializeResState(&res[i], i); - ASSERT_TRUE(ConvertResToConfig(res[i], &config[i])); + ASSERT_TRUE(ConvertResStateToDnsConfig(res[i], &config[i])); } - for (int i = 0; i < 2; ++i) { + for (unsigned i = 0; i < 2; ++i) { EXPECT_TRUE(config[i].IsValid()); CompareConfig(res[i], config[i]); CloseResState(&res[i]); diff --git a/net/dns/dns_config_service_win.cc b/net/dns/dns_config_service_win.cc index 93f72462..e089387 100644 --- a/net/dns/dns_config_service_win.cc +++ b/net/dns/dns_config_service_win.cc @@ -4,11 +4,11 @@ #include "net/dns/dns_config_service_win.h" -#include <iphlpapi.h> -#include <windows.h> - +#include <algorithm> #include <string> +#include "base/bind.h" +#include "base/callback.h" #include "base/compiler_specific.h" #include "base/file_path.h" #include "base/memory/scoped_ptr.h" @@ -18,15 +18,96 @@ #include "base/utf_string_conversions.h" #include "base/win/object_watcher.h" #include "base/win/registry.h" +#include "base/win/windows_version.h" #include "googleurl/src/url_canon.h" #include "net/base/net_util.h" #include "net/dns/serial_worker.h" #include "net/dns/watching_file_reader.h" +#pragma comment(lib, "iphlpapi.lib") + namespace net { +namespace { + +// Watches a single registry key for changes. This class is thread-safe with +// the exception of StartWatch which must be called once and only once. +class RegistryWatcher : public base::win::ObjectWatcher::Delegate { + public: + RegistryWatcher() {} + + bool StartWatch(const wchar_t* key, const base::Closure& callback) { + DCHECK(!callback.is_null()); + DCHECK(callback_.is_null()); + // TODO(szym): This will need to change to address http://crbug.com/99509 + DCHECK(!key_.Valid()); + if (key_.Open(HKEY_LOCAL_MACHINE, key, + KEY_NOTIFY | KEY_QUERY_VALUE) != ERROR_SUCCESS) + return false; + if (key_.StartWatching() != ERROR_SUCCESS) + return false; + if (!watcher_.StartWatching(key_.watch_event(), this)) + return false; + callback_ = callback; + return true; + } + + void Cancel() { + callback_.Reset(); + key_.StopWatching(); + watcher_.StopWatching(); + } + + virtual void OnObjectSignaled(HANDLE object) OVERRIDE { + if (!callback_.is_null()) + callback_.Run(); + // TODO(szym): handle errors + bool success = key_.StartWatching() && + watcher_.StartWatching(key_.watch_event(), this); + DCHECK(success); + } + + bool ReadString(const wchar_t* name, + DnsSystemSettings::RegString* out) const { + out->set = false; + if (!key_.Valid()) { + // Assume that if the |key_| is invalid then the key is missing. + return true; + } + LONG result = key_.ReadValue(name, &out->value); + if (result == ERROR_SUCCESS) { + out->set = true; + return true; + } + return (result == ERROR_FILE_NOT_FOUND); + } + + bool ReadDword(const wchar_t* name, + DnsSystemSettings::RegDword* out) const { + out->set = false; + if (!key_.Valid()) { + // Assume that if the |key_| is invalid then the key is missing. + return true; + } + LONG result = key_.ReadValueDW(name, &out->value); + if (result == ERROR_SUCCESS) { + out->set = true; + return true; + } + return (result == ERROR_FILE_NOT_FOUND); + } + + private: + base::Closure callback_; + base::win::RegKey key_; + base::win::ObjectWatcher watcher_; + + DISALLOW_COPY_AND_ASSIGN(RegistryWatcher); +}; + // Converts a string16 domain name to ASCII, possibly using punycode. -// Returns true if the conversion succeeds. +// Returns true if the conversion succeeds. In case of failure, |domain| might +// become dirty. bool ParseDomainASCII(const string16& widestr, std::string* domain) { DCHECK(domain); // Check if already ASCII. @@ -38,117 +119,213 @@ bool ParseDomainASCII(const string16& widestr, std::string* domain) { // Otherwise try to convert it from IDN to punycode. const int kInitialBufferSize = 256; url_canon::RawCanonOutputT<char16, kInitialBufferSize> punycode; - if (!url_canon::IDNToASCII(widestr.data(), - widestr.length(), - &punycode)) { + if (!url_canon::IDNToASCII(widestr.data(), widestr.length(), &punycode)) { return false; } // |punycode_output| should now be ASCII; convert it to a std::string. // (We could use UTF16ToASCII() instead, but that requires an extra string // copy. Since ASCII is a subset of UTF8 the following is equivalent). - bool success = UTF16ToUTF8(punycode.data(), - punycode.length(), - domain); + bool success = UTF16ToUTF8(punycode.data(), punycode.length(), domain); DCHECK(success); DCHECK(IsStringASCII(*domain)); return success; } -bool ParseSearchList(const string16& value, std::vector<std::string>* out) { - DCHECK(out); +} // namespace + +bool ParseSearchList(const string16& value, std::vector<std::string>* output) { + DCHECK(output); if (value.empty()) return false; + output->clear(); + // If the list includes an empty hostname (",," or ", ,"), it is terminated. // Although nslookup and network connection property tab ignore such // fragments ("a,b,,c" becomes ["a", "b", "c"]), our reference is getaddrinfo // (which sees ["a", "b"]). WMI queries also return a matching search list. std::vector<string16> woutput; base::SplitString(value, ',', &woutput); - std::vector<std::string> output; for (size_t i = 0; i < woutput.size(); ++i) { - // Convert non-ascii to punycode, although getaddrinfo does not properly + // Convert non-ASCII to punycode, although getaddrinfo does not properly // handle such suffixes. const string16& t = woutput[i]; std::string parsed; if (t.empty() || !ParseDomainASCII(t, &parsed)) break; - output.push_back(parsed); + output->push_back(parsed); } - if (output.empty()) - return false; - out->swap(output); - return true; + return !output->empty(); } -// Watches registry for changes and reads config from registry and IP helper. -// Reading and opening of reg keys is always performed on WorkerPool. Setting -// up watches requires IO loop. -class DnsConfigServiceWin::ConfigReader : public SerialWorker { - public: - // Watches a single registry key for changes. - class RegistryWatcher : public base::win::ObjectWatcher::Delegate { - public: - explicit RegistryWatcher(ConfigReader* reader) : reader_(reader) {} - - bool StartWatch(const wchar_t* key) { - DCHECK(!key_.IsWatching()); - if (key_.Open(HKEY_LOCAL_MACHINE, key, - KEY_NOTIFY | KEY_QUERY_VALUE) != ERROR_SUCCESS) +bool ConvertSettingsToDnsConfig(const DnsSystemSettings& settings, + DnsConfig* config) { + *config = DnsConfig(); + + // Use GetAdapterAddresses to get effective DNS server order and + // connection-specific DNS suffix. Ignore disconnected and loopback adapters. + // TODO(szym): Also ignore VM adapters. http://crbug.com/112856 + for (const IP_ADAPTER_ADDRESSES* adapter = settings.addresses.get(); + adapter != NULL; + adapter = adapter->Next) { + if (adapter->OperStatus != IfOperStatusUp) + continue; + if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK) + continue; + + for (const IP_ADAPTER_DNS_SERVER_ADDRESS* address = + adapter->FirstDnsServerAddress; + address != NULL; + address = address->Next) { + IPEndPoint ipe; + if (ipe.FromSockAddr(address->Address.lpSockaddr, + address->Address.iSockaddrLength)) { + config->nameservers.push_back(ipe); + } else { return false; - if (key_.StartWatching() != ERROR_SUCCESS) - return false; - if (!watcher_.StartWatching(key_.watch_event(), this)) - return false; - return true; + } } - void Cancel() { - reader_ = NULL; - key_.StopWatching(); - watcher_.StopWatching(); + // IP_ADAPTER_ADDRESSES in Vista+ has a search list at |FirstDnsSuffix|, + // but it came up empty in all trials. + // |DnsSuffix| stores the effective connection-specific suffix, which is + // obtained via DHCP (regkey: Tcpip\Parameters\Interfaces\{XXX}\DhcpDomain) + // or specified by the user (regkey: Tcpip\Parameters\Domain). + std::string dns_suffix; + if (ParseDomainASCII(adapter->DnsSuffix, &dns_suffix)) { + config->search.push_back(dns_suffix); } + } - void OnObjectSignaled(HANDLE object) OVERRIDE { - if (reader_) - reader_->WorkNow(); - // TODO(szym): handle errors - key_.StartWatching(); - watcher_.StartWatching(key_.watch_event(), this); + if (config->nameservers.empty()) + return false; // No point continuing. + + // Windows always tries a multi-label name "as is" before using suffixes. + config->ndots = 1; + + if (!settings.append_to_multi_label_name.set) { + // The default setting is true for XP, false for Vista+. + if (base::win::GetVersion() >= base::win::VERSION_VISTA) { + config->append_to_multi_label_name = false; + } else { + config->append_to_multi_label_name = true; } + } else { + config->append_to_multi_label_name = + (settings.append_to_multi_label_name.value != 0); + } - bool Read(const wchar_t* name, string16* value) const { - return key_.ReadValue(name, value) == ERROR_SUCCESS; + // SearchList takes precedence, so check it first. + if (settings.policy_search_list.set) { + std::vector<std::string> search; + if (ParseSearchList(settings.policy_search_list.value, &search)) { + config->search.swap(search); + return true; + } + // Even if invalid, the policy disables the user-specified setting below. + } else if (settings.tcpip_search_list.set) { + std::vector<std::string> search; + if (ParseSearchList(settings.tcpip_search_list.value, &search)) { + config->search.swap(search); + return true; } + } + + if (!settings.tcpip_domain.set) + return true; - private: - ConfigReader* reader_; - base::win::RegKey key_; - base::win::ObjectWatcher watcher_; - }; + std::string primary_suffix; + if (!ParseDomainASCII(settings.tcpip_domain.value, &primary_suffix)) + return true; // No primary suffix, hence no devolution. + + // Primary suffix goes in front. + config->search.insert(config->search.begin(), primary_suffix); + + // Devolution is determined by precedence: policy > dnscache > tcpip. + // |enabled|: UseDomainNameDevolution and |level|: DomainNameDevolutionLevel + // are overridden independently. + DnsSystemSettings::DevolutionSetting devolution = settings.policy_devolution; + + if (!devolution.enabled.set) + devolution.enabled = settings.dnscache_devolution.enabled; + if (!devolution.enabled.set) + devolution.enabled = settings.tcpip_devolution.enabled; + if (devolution.enabled.set && (devolution.enabled.value == 0)) + return true; // Devolution disabled. + + // By default devolution is enabled. + + if (!devolution.level.set) + devolution.level = settings.dnscache_devolution.level; + if (!devolution.level.set) + devolution.level = settings.tcpip_devolution.level; + + // After the recent update, Windows will try to determine a safe default + // value by comparing the forest root domain (FRD) to the primary suffix. + // See http://support.microsoft.com/kb/957579 for details. + // For now, if the level is not set, we disable devolution, assuming that + // we will fallback to the system getaddrinfo anyway. This might cause + // performance loss for resolutions which depend on the system default + // devolution setting. + // + // If the level is explicitly set below 2, devolution is disabled. + if (!devolution.level.set || devolution.level.value < 2) + return true; // Devolution disabled. + + // Devolve the primary suffix. This naive logic matches the observed + // behavior (see also ParseSearchList). If a suffix is not valid, it will be + // discarded when the fully-qualified name is converted to DNS format. + + unsigned num_dots = std::count(primary_suffix.begin(), + primary_suffix.end(), '.'); + + for (size_t offset = 0; num_dots >= devolution.level.value; --num_dots) { + offset = primary_suffix.find('.', offset + 1); + config->search.push_back(primary_suffix.substr(offset + 1)); + } + return true; +} + +// Watches registry for changes and reads config from registry and IP helper. +// Reading and opening of reg keys is always performed on WorkerPool. Setting +// up watches requires IO loop. +class DnsConfigServiceWin::ConfigReader : public SerialWorker { + public: explicit ConfigReader(DnsConfigServiceWin* service) - : policy_watcher_(this), - tcpip_watcher_(this), - service_(service) {} + : service_(service), + success_(false) {} bool StartWatch() { DCHECK(loop()->BelongsToCurrentThread()); // This is done only once per lifetime so open the keys on this thread. base::ThreadRestrictions::ScopedAllowIO allow_io; - if (!tcpip_watcher_.StartWatch( - L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters")) - return false; - // DNS suffix search list can be installed via group policy which sets - // this registry key. If the key is missing, the policy does not apply, - // and the DNS client uses DHCP and manual settings. + base::Closure callback = base::Bind(&SerialWorker::WorkNow, + base::Unretained(this)); + + // DNS suffix search list and devolution can be configured via group + // policy which sets this registry key. If the key is missing, the policy + // does not apply, and the DNS client uses Tcpip and Dnscache settings. // If a policy is installed, DnsConfigService will need to be restarted. // BUG=99509 policy_watcher_.StartWatch( - L"Software\\Policies\\Microsoft\\Windows NT\\DNSClient"); + L"SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient", + callback); + + // The Dnscache key might also be missing. + dnscache_watcher_.StartWatch( + L"SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters", + callback); + + // The Tcpip key must be present. + if (!tcpip_watcher_.StartWatch( + L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters", + callback)) + return false; + WorkNow(); return true; } @@ -157,6 +334,7 @@ class DnsConfigServiceWin::ConfigReader : public SerialWorker { DCHECK(loop()->BelongsToCurrentThread()); SerialWorker::Cancel(); policy_watcher_.Cancel(); + dnscache_watcher_.Cancel(); tcpip_watcher_.Cancel(); } @@ -165,106 +343,96 @@ class DnsConfigServiceWin::ConfigReader : public SerialWorker { private: friend class RegistryWatcher; - virtual ~ConfigReader() {} + virtual ~ConfigReader() { + DCHECK(IsCancelled()); + } - // Uses GetAdapterAddresses to get effective DNS server order and default - // DNS suffix. - bool ReadIpHelper(DnsConfig* config) { + bool ReadIpHelper(DnsSystemSettings* settings) { base::ThreadRestrictions::AssertIOAllowed(); - ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_UNICAST | - GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_FRIENDLY_NAME; - IP_ADAPTER_ADDRESSES buf; - IP_ADAPTER_ADDRESSES* adapters = &buf; - ULONG len = sizeof(buf); - scoped_array<char> extra_buf; - - UINT rv = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, adapters, &len); - if (rv == ERROR_BUFFER_OVERFLOW) { - extra_buf.reset(new char[len]); - adapters = reinterpret_cast<IP_ADAPTER_ADDRESSES*>(extra_buf.get()); - rv = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, adapters, &len); + ULONG flags = GAA_FLAG_SKIP_ANYCAST | + GAA_FLAG_SKIP_UNICAST | + GAA_FLAG_SKIP_MULTICAST | + GAA_FLAG_SKIP_FRIENDLY_NAME; + ULONG len = 15000; // As recommended by MSDN for GetAdaptersAddresses. + UINT rv = ERROR_BUFFER_OVERFLOW; + // Try up to three times. + for (unsigned tries = 0; + (tries < 3) && (rv == ERROR_BUFFER_OVERFLOW); + tries++) { + settings->addresses.reset( + reinterpret_cast<PIP_ADAPTER_ADDRESSES>(malloc(len))); + rv = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, + settings->addresses.get(), &len); } - if (rv != NO_ERROR) - return false; - - config->search.clear(); - config->nameservers.clear(); - - IP_ADAPTER_ADDRESSES* adapter; - for (adapter = adapters; adapter != NULL; adapter = adapter->Next) { - if (adapter->OperStatus != IfOperStatusUp) - continue; - if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK) - continue; - - // IP_ADAPTER_ADDRESSES in Vista+ has a search list at FirstDnsSuffix, - // but it's only the per-interface search list. First initialize with - // DnsSuffix, then override from registry. - std::string dns_suffix; - if (ParseDomainASCII(adapter->DnsSuffix, &dns_suffix)) { - config->search.push_back(dns_suffix); - } - IP_ADAPTER_DNS_SERVER_ADDRESS* address; - for (address = adapter->FirstDnsServerAddress; address != NULL; - address = address->Next) { - IPEndPoint ipe; - if (ipe.FromSockAddr(address->Address.lpSockaddr, - address->Address.iSockaddrLength)) { - config->nameservers.push_back(ipe); - } else { - return false; - } - } - } - return true; + return (rv == NO_ERROR); } - // Read and parse SearchList from registry. Only accept the value if all - // elements of the search list are non-empty and can be converted to UTF-8. - bool ReadSearchList(const RegistryWatcher& watcher, DnsConfig* config) { - string16 value; - if (!watcher.Read(L"SearchList", &value)) - return false; - return ParseSearchList(value, &(config->search)); + bool ReadDevolutionSetting(const RegistryWatcher& watcher, + DnsSystemSettings::DevolutionSetting& setting) { + return watcher.ReadDword(L"UseDomainNameDevolution", &setting.enabled) && + watcher.ReadDword(L"DomainNameDevolutionLevel", &setting.level); } - void DoWork() OVERRIDE { + virtual void DoWork() OVERRIDE { // Should be called on WorkerPool. - dns_config_ = DnsConfig(); - if (!ReadIpHelper(&dns_config_)) + success_ = false; + + DnsSystemSettings settings; + memset(&settings, 0, sizeof(settings)); + if (!ReadIpHelper(&settings)) return; // no point reading the rest - // check global registry overrides - if (!ReadSearchList(policy_watcher_, &dns_config_)) - ReadSearchList(tcpip_watcher_, &dns_config_); + if (!policy_watcher_.ReadString(L"SearchList", + &settings.policy_search_list)) + return; + + if (!tcpip_watcher_.ReadString(L"SearchList", &settings.tcpip_search_list)) + return; + + if (!tcpip_watcher_.ReadString(L"Domain", &settings.tcpip_domain)) + return; + + if (!ReadDevolutionSetting(policy_watcher_, settings.policy_devolution)) + return; + + if (!ReadDevolutionSetting(dnscache_watcher_, + settings.dnscache_devolution)) + return; - // TODO(szym): add support for DNS suffix devolution BUG=99510 - // TODO(szym): read AppendToMultiLabelName to determine ndots BUG=109902 + if (!ReadDevolutionSetting(tcpip_watcher_, settings.tcpip_devolution)) + return; + + if (!policy_watcher_.ReadDword(L"AppendToMultiLabelName", + &settings.append_to_multi_label_name)) + return; + + success_ = ConvertSettingsToDnsConfig(settings, &dns_config_); } - void OnWorkFinished() OVERRIDE { + virtual void OnWorkFinished() OVERRIDE { DCHECK(loop()->BelongsToCurrentThread()); DCHECK(!IsCancelled()); - if (dns_config_.IsValid()) { + if (success_) { service_->OnConfigRead(dns_config_); } else { - LOG(WARNING) << "Failed to read name servers from registry"; + LOG(WARNING) << "Failed to read config."; } } + DnsConfigServiceWin* service_; // Written in DoRead(), read in OnReadFinished(). No locking required. DnsConfig dns_config_; + bool success_; RegistryWatcher policy_watcher_; + RegistryWatcher dnscache_watcher_; RegistryWatcher tcpip_watcher_; - - DnsConfigServiceWin* service_; }; DnsConfigServiceWin::DnsConfigServiceWin() - : config_reader_(new ConfigReader(this)), - hosts_watcher_(new WatchingFileReader()) {} + : config_reader_(new ConfigReader(this)), + hosts_watcher_(new WatchingFileReader()) {} DnsConfigServiceWin::~DnsConfigServiceWin() { DCHECK(CalledOnValidThread()); diff --git a/net/dns/dns_config_service_win.h b/net/dns/dns_config_service_win.h index 97dcf6c..d60ae04 100644 --- a/net/dns/dns_config_service_win.h +++ b/net/dns/dns_config_service_win.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -6,6 +6,11 @@ #define NET_DNS_DNS_CONFIG_SERVICE_WIN_H_ #pragma once +// The sole purpose of dns_config_service_win.h is for unittests so we just +// include these headers here. +#include <winsock2.h> +#include <iphlpapi.h> + #include <string> #include <vector> @@ -15,6 +20,17 @@ #include "net/base/net_export.h" #include "net/dns/dns_config_service.h" +// The general effort of DnsConfigServiceWin is to configure |nameservers| and +// |search| in DnsConfig. The settings are stored in the Windows registry, but +// to simplify the task we use the IP Helper API wherever possible. That API +// yields the complete and ordered |nameservers|, but to determine |search| we +// need to use the registry. On Windows 7, WMI does return the correct |search| +// but on earlier versions it is insufficient. +// +// Experimental evaluation of Windows behavior suggests that domain parsing is +// naive. Domain suffixes in |search| are not validated until they are appended +// to the resolved name. We attempt to replicate this behavior. + namespace net { class WatchingFileReader; @@ -35,13 +51,60 @@ class NET_EXPORT_PRIVATE DnsConfigServiceWin DISALLOW_COPY_AND_ASSIGN(DnsConfigServiceWin); }; +// All relevant settings read from registry and IP Helper. This isolates our +// logic from system calls and is exposed for unit tests. Keep it an aggregate +// struct for easy initialization. +struct NET_EXPORT_PRIVATE DnsSystemSettings { + // The |set| flag distinguishes between empty and unset values. + struct RegString { + bool set; + string16 value; + }; + + struct RegDword { + bool set; + DWORD value; + }; + + struct DevolutionSetting { + // UseDomainNameDevolution + RegDword enabled; + // DomainNameDevolutionLevel + RegDword level; + }; + + // Filled in by GetAdapterAddresses. + scoped_ptr_malloc<IP_ADAPTER_ADDRESSES> addresses; + + // SOFTWARE\Policies\Microsoft\Windows NT\DNSClient\SearchList + RegString policy_search_list; + // SYSTEM\CurrentControlSet\Tcpip\Parameters\SearchList + RegString tcpip_search_list; + // SYSTEM\CurrentControlSet\Tcpip\Parameters\Domain + RegString tcpip_domain; + + // SOFTWARE\Policies\Microsoft\Windows NT\DNSClient + DevolutionSetting policy_devolution; + // SYSTEM\CurrentControlSet\Dnscache\Parameters + DevolutionSetting dnscache_devolution; + // SYSTEM\CurrentControlSet\Tcpip\Parameters + DevolutionSetting tcpip_devolution; + + // SOFTWARE\Policies\Microsoft\Windows NT\DNSClient\AppendToMultiLabelName + RegDword append_to_multi_label_name; +}; + // Parses |value| as search list (comma-delimited list of domain names) from -// a registry key and stores it in |out|. Returns true on success. -// Empty entries (e.g., "chromium.org,,org") terminate the list. -// Non-ascii hostnames are converted to punycode. +// a registry key and stores it in |out|. Returns true on success. Empty +// entries (e.g., "chromium.org,,org") terminate the list. Non-ascii hostnames +// are converted to punycode. bool NET_EXPORT_PRIVATE ParseSearchList(const string16& value, std::vector<std::string>* out); +// Fills in |dns_config| from |settings|. Exposed for tests. +bool NET_EXPORT_PRIVATE ConvertSettingsToDnsConfig( + const DnsSystemSettings& settings, DnsConfig* dns_config); + } // namespace net #endif // NET_DNS_DNS_CONFIG_SERVICE_WIN_H_ diff --git a/net/dns/dns_config_service_win_unittest.cc b/net/dns/dns_config_service_win_unittest.cc index 6397c32..917656b 100644 --- a/net/dns/dns_config_service_win_unittest.cc +++ b/net/dns/dns_config_service_win_unittest.cc @@ -1,16 +1,17 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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_win.h" +#include "base/win/windows_version.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { namespace { -TEST(DnsConfigServiceWinTest, ParseDomain) { +TEST(DnsConfigServiceWinTest, ParseSearchList) { const struct TestCase { const wchar_t* input; const char* output[4]; // NULL-terminated, empty if expected false @@ -25,26 +26,301 @@ TEST(DnsConfigServiceWinTest, ParseDomain) { // Empty search list is invalid { L"", { NULL } }, { L",,", { NULL } }, - { NULL, { NULL } }, }; std::vector<std::string> actual_output, expected_output; - for (const TestCase* t = cases; t->input; ++t) { + for (unsigned i = 0; i < arraysize(cases); ++i) { + const TestCase& t = cases[i]; actual_output.clear(); actual_output.push_back("UNSET"); expected_output.clear(); - for (const char* const* output = t->output; *output; ++output) { + for (const char* const* output = t.output; *output; ++output) { expected_output.push_back(*output); } - bool result = ParseSearchList(t->input, &actual_output); + bool result = ParseSearchList(t.input, &actual_output); if (!expected_output.empty()) { EXPECT_TRUE(result); EXPECT_EQ(expected_output, actual_output); } else { - EXPECT_FALSE(result) << "Unexpected parse success on " << t->input; - expected_output.push_back("UNSET"); - EXPECT_EQ(expected_output, actual_output); + EXPECT_FALSE(result) << "Unexpected parse success on " << t.input; + } + } +} + +struct AdapterInfo { + IFTYPE if_type; + IF_OPER_STATUS oper_status; + PWCHAR dns_suffix; + std::string dns_server_addresses[4]; // Empty string indicates end. +}; + +scoped_ptr_malloc<IP_ADAPTER_ADDRESSES> CreateAdapterAddresses( + const AdapterInfo* infos) { + size_t num_adapters = 0; + size_t num_addresses = 0; + for (size_t i = 0; infos[i].if_type; ++i) { + ++num_adapters; + for (size_t j = 0; !infos[i].dns_server_addresses[j].empty(); ++j) { + ++num_addresses; + } + } + + size_t heap_size = num_adapters * sizeof(IP_ADAPTER_ADDRESSES) + + num_addresses * (sizeof(IP_ADAPTER_DNS_SERVER_ADDRESS) + + sizeof(struct sockaddr_storage)); + scoped_ptr_malloc<IP_ADAPTER_ADDRESSES> heap( + reinterpret_cast<IP_ADAPTER_ADDRESSES*>(malloc(heap_size))); + CHECK(heap.get()); + memset(heap.get(), 0, heap_size); + + IP_ADAPTER_ADDRESSES* adapters = heap.get(); + IP_ADAPTER_DNS_SERVER_ADDRESS* addresses = + reinterpret_cast<IP_ADAPTER_DNS_SERVER_ADDRESS*>(adapters + num_adapters); + struct sockaddr_storage* storage = + reinterpret_cast<struct sockaddr_storage*>(addresses + num_addresses); + + for (size_t i = 0; i < num_adapters; ++i) { + const AdapterInfo& info = infos[i]; + IP_ADAPTER_ADDRESSES* adapter = adapters + i; + if (i + 1 < num_adapters) + adapter->Next = adapter + 1; + adapter->IfType = info.if_type; + adapter->OperStatus = info.oper_status; + adapter->DnsSuffix = info.dns_suffix; + IP_ADAPTER_DNS_SERVER_ADDRESS* address; + for (size_t j = 0; !info.dns_server_addresses[j].empty(); ++j) { + --num_addresses; + if (j == 0) { + address = adapter->FirstDnsServerAddress = addresses + num_addresses; + } else { + address = address->Next = address + 1; + } + IPAddressNumber ip; + CHECK(ParseIPLiteralToNumber(info.dns_server_addresses[j], &ip)); + IPEndPoint ipe(ip, 53); + address->Address.lpSockaddr = + reinterpret_cast<LPSOCKADDR>(storage + num_addresses); + size_t length = sizeof(struct sockaddr_storage); + CHECK(ipe.ToSockAddr(address->Address.lpSockaddr, &length)); + address->Address.iSockaddrLength = static_cast<int>(length); + } + } + + return heap.Pass(); +} + +TEST(DnsConfigServiceWinTest, ConvertAdaptersAddresses) { + // Check nameservers and connection-specific suffix. + const struct TestCase { + AdapterInfo input_adapters[4]; // |if_type| == 0 indicates end. + std::string expected_nameservers[4]; // Empty string indicates end. + std::string expected_suffix; + } cases[] = { + { // Ignore loopback and inactive adapters. + { + { IF_TYPE_SOFTWARE_LOOPBACK, IfOperStatusUp, L"funnyloop", + { "2.0.0.2" } }, + { IF_TYPE_FASTETHER, IfOperStatusDormant, L"example.com", + { "1.0.0.1" } }, + { IF_TYPE_USB, IfOperStatusUp, L"chromium.org", + { "10.0.0.10", "2001:FFFF::1111" } }, + { 0 }, + }, + { "10.0.0.10", "2001:FFFF::1111" }, + "chromium.org", + }, + { // No usable nameservers. + { + { IF_TYPE_SOFTWARE_LOOPBACK, IfOperStatusUp, L"localhost", + { "2.0.0.2" } }, + { IF_TYPE_FASTETHER, IfOperStatusDormant, L"example.com", + { "1.0.0.1" } }, + { IF_TYPE_USB, IfOperStatusUp, L"chromium.org" }, + { 0 }, + }, + }, + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + const TestCase& t = cases[i]; + DnsSystemSettings settings = { + CreateAdapterAddresses(t.input_adapters), + // Default settings for the rest. + }; + std::vector<IPEndPoint> expected_nameservers; + for (size_t j = 0; !t.expected_nameservers[j].empty(); ++j) { + IPAddressNumber ip; + ASSERT_TRUE(ParseIPLiteralToNumber(t.expected_nameservers[j], &ip)); + expected_nameservers.push_back(IPEndPoint(ip, 53)); + } + + DnsConfig config; + bool result = ConvertSettingsToDnsConfig(settings, &config); + bool expected_result = !expected_nameservers.empty(); + ASSERT_EQ(expected_result, result); + EXPECT_EQ(expected_nameservers, config.nameservers); + if (result) { + ASSERT_EQ(1u, config.search.size()); + EXPECT_EQ(t.expected_suffix, config.search[0]); + } + } +} + +TEST(DnsConfigServiceWinTest, ConvertSuffixSearch) { + AdapterInfo infos[2] = { + { IF_TYPE_USB, IfOperStatusUp, L"connection.suffix", { "1.0.0.1" } }, + { 0 }, + }; + + const struct TestCase { + DnsSystemSettings input_settings; + std::string expected_search[5]; + } cases[] = { + { // Policy SearchList override. + { + CreateAdapterAddresses(infos), + { true, L"policy.searchlist.a,policy.searchlist.b" }, + { true, L"tcpip.searchlist.a,tcpip.searchlist.b" }, + { true, L"tcpip.domain" }, + }, + { "policy.searchlist.a", "policy.searchlist.b" }, + }, + { // User-specified SearchList override. + { + CreateAdapterAddresses(infos), + { false }, + { true, L"tcpip.searchlist.a,tcpip.searchlist.b" }, + { true, L"tcpip.domain" }, + }, + { "tcpip.searchlist.a", "tcpip.searchlist.b" }, + }, + { // Void SearchList. + { + CreateAdapterAddresses(infos), + { true, L",bad.searchlist,parsed.as.empty" }, + { true, L"tcpip.searchlist,good.but.overridden" }, + { true, L"tcpip.domain" }, + }, + { "tcpip.domain", "connection.suffix" }, + }, + { // Devolution enabled by policy, level by dnscache. + { + CreateAdapterAddresses(infos), + { false }, + { false }, + { true, L"a.b.c.d.e" }, + { { true, 1 }, { false } }, + { { true, 0 }, { true, 3 } }, + { { true, 0 }, { true, 1 } }, + }, + { "a.b.c.d.e", "connection.suffix", "b.c.d.e", "c.d.e" }, + }, + { // Devolution enabled by dnscache, level by policy. + { + CreateAdapterAddresses(infos), + { false }, + { false }, + { true, L"a.b.c.d.e" }, + { { false }, { true, 4 } }, + { { true, 1 }, { false } }, + { { true, 0 }, { true, 3 } }, + }, + { "a.b.c.d.e", "connection.suffix", "b.c.d.e" }, + }, + { // Devolution enabled by default. + { + CreateAdapterAddresses(infos), + { false }, + { false }, + { true, L"a.b.c.d.e" }, + { { false }, { false } }, + { { false }, { true, 3 } }, + { { false }, { true, 1 } }, + }, + { "a.b.c.d.e", "connection.suffix", "b.c.d.e", "c.d.e" }, + }, + { // Devolution disabled when no explicit level. + // Windows XP and Vista use a default level = 2, but we don't. + { + CreateAdapterAddresses(infos), + { false }, + { false }, + { true, L"a.b.c.d.e" }, + { { true, 1 }, { false } }, + { { true, 1 }, { false } }, + { { true, 1 }, { false } }, + }, + { "a.b.c.d.e", "connection.suffix" }, + }, + { // Devolution disabled by policy level. + { + CreateAdapterAddresses(infos), + { false }, + { false }, + { true, L"a.b.c.d.e" }, + { { false }, { true, 1 } }, + { { true, 1 }, { true, 3 } }, + { { true, 1 }, { true, 4 } }, + }, + { "a.b.c.d.e", "connection.suffix" }, + }, + { // Devolution disabled by user setting. + { + CreateAdapterAddresses(infos), + { false }, + { false }, + { true, L"a.b.c.d.e" }, + { { false }, { true, 3 } }, + { { false }, { true, 3 } }, + { { true, 0 }, { true, 3 } }, + }, + { "a.b.c.d.e", "connection.suffix" }, + }, + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + const TestCase& t = cases[i]; + DnsConfig config; + ASSERT_TRUE(ConvertSettingsToDnsConfig(t.input_settings, &config)); + std::vector<std::string> expected_search; + for (size_t j = 0; !t.expected_search[j].empty(); ++j) { + expected_search.push_back(t.expected_search[j]); } + EXPECT_EQ(expected_search, config.search); + } +} + +TEST(DnsConfigServiceWinTest, AppendToMultiLabelName) { + AdapterInfo infos[2] = { + { IF_TYPE_USB, IfOperStatusUp, L"connection.suffix", { "1.0.0.1" } }, + { 0 }, + }; + + // The default setting was true pre-Vista. + bool default_value = (base::win::GetVersion() < base::win::VERSION_VISTA); + + const struct TestCase { + DnsSystemSettings::RegDword input; + bool expected_output; + } cases[] = { + { { true, 0 }, false }, + { { true, 1 }, true }, + { { false, 0 }, default_value }, + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + const TestCase& t = cases[i]; + DnsSystemSettings settings = { + CreateAdapterAddresses(infos), + { false }, { false }, { false }, + { { false }, { false } }, + { { false }, { false } }, + { { false }, { false } }, + t.input, + }; + DnsConfig config; + ASSERT_TRUE(ConvertSettingsToDnsConfig(settings, &config)); + EXPECT_EQ(config.append_to_multi_label_name, t.expected_output); } } diff --git a/net/dns/dns_transaction.cc b/net/dns/dns_transaction.cc index 8222863..6cd02a3 100644 --- a/net/dns/dns_transaction.cc +++ b/net/dns/dns_transaction.cc @@ -308,22 +308,28 @@ class DnsTransactionImpl : public DnsTransaction, public base::NonThreadSafe { int PrepareSearch() { const DnsConfig& config = session_->config(); - std::string labelled_hostname; - if (!DNSDomainFromDot(hostname_, &labelled_hostname)) + std::string labeled_hostname; + if (!DNSDomainFromDot(hostname_, &labeled_hostname)) return ERR_INVALID_ARGUMENT; if (hostname_[hostname_.size() - 1] == '.') { // It's a fully-qualified name, no suffix search. - qnames_.push_back(labelled_hostname); + qnames_.push_back(labeled_hostname); return OK; } - // Set true when |labelled_hostname| is put on the list. + int ndots = CountLabels(labeled_hostname) - 1; + + if (ndots > 0 && !config.append_to_multi_label_name) { + qnames_.push_back(labeled_hostname); + return OK; + } + + // Set true when |labeled_hostname| is put on the list. bool had_hostname = false; - int ndots = CountLabels(labelled_hostname) - 1; if (ndots >= config.ndots) { - qnames_.push_back(labelled_hostname); + qnames_.push_back(labeled_hostname); had_hostname = true; } @@ -332,7 +338,7 @@ class DnsTransactionImpl : public DnsTransaction, public base::NonThreadSafe { // Ignore invalid (too long) combinations. if (!DNSDomainFromDot(hostname_ + "." + config.search[i], &qname)) continue; - if (qname.size() == labelled_hostname.size()) { + if (qname.size() == labeled_hostname.size()) { if (had_hostname) continue; had_hostname = true; @@ -340,10 +346,10 @@ class DnsTransactionImpl : public DnsTransaction, public base::NonThreadSafe { qnames_.push_back(qname); } - if (!had_hostname) - qnames_.push_back(labelled_hostname); + if (ndots > 0 && !had_hostname) + qnames_.push_back(labeled_hostname); - return OK; + return qnames_.empty() ? ERR_NAME_NOT_RESOLVED : OK; } void DoCallback(int rv, const DnsUDPAttempt* successful_attempt) { diff --git a/net/dns/dns_transaction_unittest.cc b/net/dns/dns_transaction_unittest.cc index 7c7e9e4..d3eadb9 100644 --- a/net/dns/dns_transaction_unittest.cc +++ b/net/dns/dns_transaction_unittest.cc @@ -117,6 +117,7 @@ class TransactionHelper { if (rv != ERR_IO_PENDING) { EXPECT_NE(OK, rv); EXPECT_EQ(expected_answer_count_, rv); + completed_ = true; } } @@ -162,6 +163,22 @@ class TransactionHelper { return completed_; } + // Shorthands for commonly used commands. + + bool Run(DnsTransactionFactory* factory) { + StartTransaction(factory); + MessageLoop::current()->RunAllPending(); + return has_completed(); + } + + // Use when some of the responses are timeouts. + bool RunUntilDone(DnsTransactionFactory* factory) { + set_quit_in_callback(); + StartTransaction(factory); + MessageLoop::current()->Run(); + return has_completed(); + } + private: std::string hostname_; uint16 qtype_; @@ -179,7 +196,7 @@ class DnsTransactionTest : public testing::Test { // Generates |nameservers| for DnsConfig. void ConfigureNumServers(unsigned num_servers) { - DCHECK_LT(num_servers, 255u); + CHECK_LE(num_servers, 255u); config_.nameservers.clear(); IPAddressNumber dns_ip; { @@ -214,7 +231,7 @@ class DnsTransactionTest : public testing::Test { uint16 id, const char* data, size_t data_length) { - DCHECK(socket_factory_); + CHECK(socket_factory_); DnsQuery* query = new DnsQuery(id, DomainFromDot(dotted_name), qtype); queries_.push_back(query); @@ -234,7 +251,7 @@ class DnsTransactionTest : public testing::Test { // Add expected query of |dotted_name| and |qtype| and no response. void AddTimeout(const char* dotted_name, uint16 qtype) { - DCHECK(socket_factory_); + CHECK(socket_factory_); uint16 id = base::RandInt(0, kuint16max); DnsQuery* query = new DnsQuery(id, DomainFromDot(dotted_name), qtype); queries_.push_back(query); @@ -250,8 +267,8 @@ class DnsTransactionTest : public testing::Test { // Add expected query of |dotted_name| and |qtype| and response with no answer // and rcode set to |rcode|. void AddRcode(const char* dotted_name, uint16 qtype, int rcode) { - DCHECK(socket_factory_); - DCHECK_NE(dns_protocol::kRcodeNOERROR, rcode); + CHECK(socket_factory_); + CHECK_NE(dns_protocol::kRcodeNOERROR, rcode); uint16 id = base::RandInt(0, kuint16max); DnsQuery* query = new DnsQuery(id, DomainFromDot(dotted_name), qtype); queries_.push_back(query); @@ -277,12 +294,13 @@ class DnsTransactionTest : public testing::Test { // This separation is necessary because the |reads_| and |writes_| vectors // could reallocate their data during those calls. void PrepareSockets() { - DCHECK_EQ(writes_.size(), reads_.size()); + CHECK_EQ(writes_.size(), reads_.size()); for (size_t i = 0; i < writes_.size(); ++i) { - SocketDataProvider *data; + DelayedSocketData* data; if (reads_[i].data) { data = new DelayedSocketData(1, &reads_[i], 1, &writes_[i], 1); } else { + // Timeout. data = new DelayedSocketData(2, NULL, 0, &writes_[i], 1); } socket_data_.push_back(data); @@ -310,6 +328,13 @@ class DnsTransactionTest : public testing::Test { ConfigureFactory(); } + void TearDown() OVERRIDE { + // Check that all socket data was at least written to. + for (size_t i = 0; i < socket_data_.size(); ++i) { + EXPECT_GT(socket_data_[i]->write_index(), 0u); + } + } + protected: int GetNextId(int min, int max) { EXPECT_FALSE(transaction_ids_.empty()); @@ -331,7 +356,7 @@ class DnsTransactionTest : public testing::Test { std::vector<MockWrite> writes_; // Holder for the socket data (MockClientSocketFactory does not own it). - ScopedVector<SocketDataProvider> socket_data_; + ScopedVector<DelayedSocketData> socket_data_; std::deque<int> transaction_ids_; // Owned by |session_|. @@ -351,12 +376,7 @@ TEST_F(DnsTransactionTest, Lookup) { TransactionHelper helper0(kT0HostName, kT0Qtype, arraysize(kT0IpAddresses) + 1); - helper0.StartTransaction(transaction_factory_.get()); - - // Wait until result. - MessageLoop::current()->RunAllPending(); - - EXPECT_TRUE(helper0.has_completed()); + EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } // Concurrent lookup tests assume that DnsTransaction::Start immediately @@ -452,11 +472,7 @@ TEST_F(DnsTransactionTest, CancelFromCallback) { kT0Qtype, arraysize(kT0IpAddresses) + 1); helper0.set_cancel_in_callback(); - helper0.StartTransaction(transaction_factory_.get()); - - MessageLoop::current()->RunAllPending(); - - EXPECT_TRUE(helper0.has_completed()); + EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, ServerFail) { @@ -466,11 +482,7 @@ TEST_F(DnsTransactionTest, ServerFail) { TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_SERVER_FAILED); - helper0.StartTransaction(transaction_factory_.get()); - - MessageLoop::current()->RunAllPending(); - - EXPECT_TRUE(helper0.has_completed()); + EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, NoDomain) { @@ -480,11 +492,7 @@ TEST_F(DnsTransactionTest, NoDomain) { TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_NAME_NOT_RESOLVED); - helper0.StartTransaction(transaction_factory_.get()); - - MessageLoop::current()->RunAllPending(); - - EXPECT_TRUE(helper0.has_completed()); + EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, Timeout) { @@ -502,12 +510,7 @@ TEST_F(DnsTransactionTest, Timeout) { TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_TIMED_OUT); - helper0.set_quit_in_callback(); - helper0.StartTransaction(transaction_factory_.get()); - - MessageLoop::current()->Run(); - - EXPECT_TRUE(helper0.has_completed()); + EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); MessageLoop::current()->AssertIdle(); } @@ -539,18 +542,9 @@ TEST_F(DnsTransactionTest, ServerFallbackAndRotate) { TransactionHelper helper1(kT1HostName, kT1Qtype, ERR_NAME_NOT_RESOLVED); - helper0.set_quit_in_callback(); - helper0.StartTransaction(transaction_factory_.get()); - MessageLoop::current()->Run(); - - EXPECT_TRUE(helper0.has_completed()); - - helper1.StartTransaction(transaction_factory_.get()); - - MessageLoop::current()->RunAllPending(); - - EXPECT_TRUE(helper1.has_completed()); + EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); + EXPECT_TRUE(helper1.Run(transaction_factory_.get())); unsigned kOrder[] = { 0, 1, 2, 0, 1, // The first transaction. @@ -578,11 +572,7 @@ TEST_F(DnsTransactionTest, SuffixSearchAboveNdots) { dns_protocol::kTypeA, ERR_NAME_NOT_RESOLVED); - helper0.StartTransaction(transaction_factory_.get()); - - MessageLoop::current()->RunAllPending(); - - EXPECT_TRUE(helper0.has_completed()); + EXPECT_TRUE(helper0.Run(transaction_factory_.get())); // Also check if suffix search causes server rotation. unsigned kOrder0[] = { 0, 1, 0, 1 }; @@ -602,6 +592,10 @@ TEST_F(DnsTransactionTest, SuffixSearchBelowNdots) { AddRcode("x.y.c", dns_protocol::kTypeA, dns_protocol::kRcodeNXDOMAIN); AddRcode("x.y", dns_protocol::kTypeA, dns_protocol::kRcodeNXDOMAIN); // Responses for second transaction. + AddRcode("x.a", dns_protocol::kTypeA, dns_protocol::kRcodeNXDOMAIN); + AddRcode("x.b", dns_protocol::kTypeA, dns_protocol::kRcodeNXDOMAIN); + AddRcode("x.c", dns_protocol::kTypeA, dns_protocol::kRcodeNXDOMAIN); + // Responses for third transaction. AddRcode("x", dns_protocol::kTypeAAAA, dns_protocol::kRcodeNXDOMAIN); PrepareSockets(); @@ -609,24 +603,79 @@ TEST_F(DnsTransactionTest, SuffixSearchBelowNdots) { dns_protocol::kTypeA, ERR_NAME_NOT_RESOLVED); - helper0.StartTransaction(transaction_factory_.get()); + EXPECT_TRUE(helper0.Run(transaction_factory_.get())); - MessageLoop::current()->RunAllPending(); + // A single-label name. + TransactionHelper helper1("x", + dns_protocol::kTypeA, + ERR_NAME_NOT_RESOLVED); - EXPECT_TRUE(helper0.has_completed()); + EXPECT_TRUE(helper1.Run(transaction_factory_.get())); - // Test fully-qualified name. - TransactionHelper helper1("x.", + // A fully-qualified name. + TransactionHelper helper2("x.", dns_protocol::kTypeAAAA, ERR_NAME_NOT_RESOLVED); - helper1.StartTransaction(transaction_factory_.get()); + EXPECT_TRUE(helper2.Run(transaction_factory_.get())); +} - MessageLoop::current()->RunAllPending(); +TEST_F(DnsTransactionTest, EmptySuffixSearch) { + ConfigureFactory(); + // Responses for first transaction. + AddRcode("x", dns_protocol::kTypeA, dns_protocol::kRcodeNXDOMAIN); + PrepareSockets(); + + // A fully-qualified name. + TransactionHelper helper0("x.", + dns_protocol::kTypeA, + ERR_NAME_NOT_RESOLVED); + + EXPECT_TRUE(helper0.Run(transaction_factory_.get())); + + // A single label name is not even attempted. + TransactionHelper helper1("singlelabel", + dns_protocol::kTypeA, + ERR_NAME_NOT_RESOLVED); + + helper1.StartTransaction(transaction_factory_.get()); EXPECT_TRUE(helper1.has_completed()); } +TEST_F(DnsTransactionTest, DontAppendToMultiLabelName) { + config_.search.push_back("a"); + config_.search.push_back("b"); + config_.search.push_back("c"); + config_.append_to_multi_label_name = false; + ConfigureFactory(); + + // Responses for first transaction. + AddRcode("x.y.z", dns_protocol::kTypeA, dns_protocol::kRcodeNXDOMAIN); + // Responses for second transaction. + AddRcode("x.y", dns_protocol::kTypeA, dns_protocol::kRcodeNXDOMAIN); + // Responses for third transaction. + AddRcode("x.a", dns_protocol::kTypeA, dns_protocol::kRcodeNXDOMAIN); + AddRcode("x.b", dns_protocol::kTypeA, dns_protocol::kRcodeNXDOMAIN); + AddRcode("x.c", dns_protocol::kTypeA, dns_protocol::kRcodeNXDOMAIN); + PrepareSockets(); + + TransactionHelper helper0("x.y.z", + dns_protocol::kTypeA, + ERR_NAME_NOT_RESOLVED); + EXPECT_TRUE(helper0.Run(transaction_factory_.get())); + + TransactionHelper helper1("x.y", + dns_protocol::kTypeA, + ERR_NAME_NOT_RESOLVED); + EXPECT_TRUE(helper1.Run(transaction_factory_.get())); + + TransactionHelper helper2("x", + dns_protocol::kTypeA, + ERR_NAME_NOT_RESOLVED); + EXPECT_TRUE(helper2.Run(transaction_factory_.get())); +} + } // namespace } // namespace net |