diff options
author | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-21 01:40:53 +0000 |
---|---|---|
committer | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-21 01:40:53 +0000 |
commit | f2cb3cf86e4217581443d93e863c891a05066e60 (patch) | |
tree | c62110d2347d17e1b1a1f57fa758cf8bba72548b /net/dns | |
parent | b8512263f9556ac705394e9da03d364980f0eadf (diff) | |
download | chromium_src-f2cb3cf86e4217581443d93e863c891a05066e60.zip chromium_src-f2cb3cf86e4217581443d93e863c891a05066e60.tar.gz chromium_src-f2cb3cf86e4217581443d93e863c891a05066e60.tar.bz2 |
net: move host_resolver files from net/base to net/dns
BUG=70818
Review URL: https://codereview.chromium.org/12518036
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@189485 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/dns')
-rw-r--r-- | net/dns/host_resolver.cc | 148 | ||||
-rw-r--r-- | net/dns/host_resolver.h | 208 | ||||
-rw-r--r-- | net/dns/host_resolver_impl.cc | 2180 | ||||
-rw-r--r-- | net/dns/host_resolver_impl.h | 287 | ||||
-rw-r--r-- | net/dns/host_resolver_impl_unittest.cc | 1481 | ||||
-rw-r--r-- | net/dns/host_resolver_proc.cc | 251 | ||||
-rw-r--r-- | net/dns/host_resolver_proc.h | 96 | ||||
-rw-r--r-- | net/dns/mapped_host_resolver.cc | 67 | ||||
-rw-r--r-- | net/dns/mapped_host_resolver.h | 72 | ||||
-rw-r--r-- | net/dns/mapped_host_resolver_unittest.cc | 219 | ||||
-rw-r--r-- | net/dns/mock_host_resolver.cc | 417 | ||||
-rw-r--r-- | net/dns/mock_host_resolver.h | 254 | ||||
-rw-r--r-- | net/dns/single_request_host_resolver.cc | 77 | ||||
-rw-r--r-- | net/dns/single_request_host_resolver.h | 56 | ||||
-rw-r--r-- | net/dns/single_request_host_resolver_unittest.cc | 124 |
15 files changed, 5937 insertions, 0 deletions
diff --git a/net/dns/host_resolver.cc b/net/dns/host_resolver.cc new file mode 100644 index 0000000..6350d2e --- /dev/null +++ b/net/dns/host_resolver.cc @@ -0,0 +1,148 @@ +// 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/host_resolver.h" + +#include "base/logging.h" +#include "base/metrics/field_trial.h" +#include "base/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "net/base/host_cache.h" +#include "net/dns/dns_client.h" +#include "net/dns/dns_config_service.h" +#include "net/dns/host_resolver_impl.h" + +namespace net { + +namespace { + +// Maximum of 6 concurrent resolver threads (excluding retries). +// Some routers (or resolvers) appear to start to provide host-not-found if +// too many simultaneous resolutions are pending. This number needs to be +// further optimized, but 8 is what FF currently does. We found some routers +// that limit this to 6, so we're temporarily holding it at that level. +const size_t kDefaultMaxProcTasks = 6u; + +// When configuring from field trial, do not allow +const size_t kSaneMaxProcTasks = 20u; + +PrioritizedDispatcher::Limits GetDispatcherLimits( + const HostResolver::Options& options) { + PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, + options.max_concurrent_resolves); + + // If not using default, do not use the field trial. + if (limits.total_jobs != HostResolver::kDefaultParallelism) + return limits; + + // Default, without trial is no reserved slots. + limits.total_jobs = kDefaultMaxProcTasks; + + // Parallelism is determined by the field trial. + std::string group = base::FieldTrialList::FindFullName( + "HostResolverDispatch"); + + if (group.empty()) + return limits; + + // The format of the group name is a list of non-negative integers separated + // by ':'. Each of the elements in the list corresponds to an element in + // |reserved_slots|, except the last one which is the |total_jobs|. + + std::vector<std::string> group_parts; + base::SplitString(group, ':', &group_parts); + if (group_parts.size() != NUM_PRIORITIES + 1) { + NOTREACHED(); + return limits; + } + + std::vector<size_t> parsed(group_parts.size()); + size_t total_reserved_slots = 0; + + for (size_t i = 0; i < group_parts.size(); ++i) { + if (!base::StringToSizeT(group_parts[i], &parsed[i])) { + NOTREACHED(); + return limits; + } + } + + size_t total_jobs = parsed.back(); + parsed.pop_back(); + for (size_t i = 0; i < parsed.size(); ++i) { + total_reserved_slots += parsed[i]; + } + + // There must be some unreserved slots available for the all priorities. + if (total_reserved_slots > total_jobs || + (total_reserved_slots == total_jobs && parsed[MINIMUM_PRIORITY] == 0)) { + NOTREACHED(); + return limits; + } + + limits.total_jobs = total_jobs; + limits.reserved_slots = parsed; + return limits; +} + +} // namespace + +HostResolver::Options::Options() + : max_concurrent_resolves(kDefaultParallelism), + max_retry_attempts(kDefaultRetryAttempts), + enable_caching(true) { +} + +HostResolver::RequestInfo::RequestInfo(const HostPortPair& host_port_pair) + : host_port_pair_(host_port_pair), + address_family_(ADDRESS_FAMILY_UNSPECIFIED), + host_resolver_flags_(0), + allow_cached_response_(true), + is_speculative_(false), + priority_(MEDIUM) { +} + +HostResolver::~HostResolver() { +} + +AddressFamily HostResolver::GetDefaultAddressFamily() const { + return ADDRESS_FAMILY_UNSPECIFIED; +} + +void HostResolver::ProbeIPv6Support() { +} + +void HostResolver::SetDnsClientEnabled(bool enabled) { +} + +HostCache* HostResolver::GetHostCache() { + return NULL; +} + +base::Value* HostResolver::GetDnsConfigAsValue() const { + return NULL; +} + +// static +scoped_ptr<HostResolver> +HostResolver::CreateSystemResolver(const Options& options, NetLog* net_log) { + scoped_ptr<HostCache> cache; + if (options.enable_caching) + cache = HostCache::CreateDefaultCache(); + return scoped_ptr<HostResolver>(new HostResolverImpl( + cache.Pass(), + GetDispatcherLimits(options), + HostResolverImpl::ProcTaskParams(NULL, options.max_retry_attempts), + net_log)); +} + +// static +scoped_ptr<HostResolver> +HostResolver::CreateDefaultResolver(NetLog* net_log) { + return CreateSystemResolver(Options(), net_log); +} + +HostResolver::HostResolver() { +} + +} // namespace net diff --git a/net/dns/host_resolver.h b/net/dns/host_resolver.h new file mode 100644 index 0000000..1adf8b7 --- /dev/null +++ b/net/dns/host_resolver.h @@ -0,0 +1,208 @@ +// 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. + +#ifndef NET_DNS_HOST_RESOLVER_H_ +#define NET_DNS_HOST_RESOLVER_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "net/base/address_family.h" +#include "net/base/completion_callback.h" +#include "net/base/host_port_pair.h" +#include "net/base/net_export.h" +#include "net/base/net_util.h" +#include "net/base/request_priority.h" + +namespace base { +class Value; +} + +namespace net { + +class AddressList; +class BoundNetLog; +class HostCache; +class HostResolverProc; +class NetLog; + +// This class represents the task of resolving hostnames (or IP address +// literal) to an AddressList object. +// +// HostResolver can handle multiple requests at a time, so when cancelling a +// request the RequestHandle that was returned by Resolve() needs to be +// given. A simpler alternative for consumers that only have 1 outstanding +// request at a time is to create a SingleRequestHostResolver wrapper around +// HostResolver (which will automatically cancel the single request when it +// goes out of scope). +class NET_EXPORT HostResolver { + public: + // |max_concurrent_resolves| is how many resolve requests will be allowed to + // run in parallel. Pass HostResolver::kDefaultParallelism to choose a + // default value. + // |max_retry_attempts| is the maximum number of times we will retry for host + // resolution. Pass HostResolver::kDefaultRetryAttempts to choose a default + // value. + // |enable_caching| controls whether a HostCache is used. + struct NET_EXPORT Options { + Options(); + + size_t max_concurrent_resolves; + size_t max_retry_attempts; + bool enable_caching; + }; + + // The parameters for doing a Resolve(). A hostname and port are required, + // the rest are optional (and have reasonable defaults). + class NET_EXPORT RequestInfo { + public: + explicit RequestInfo(const HostPortPair& host_port_pair); + + const HostPortPair& host_port_pair() const { return host_port_pair_; } + void set_host_port_pair(const HostPortPair& host_port_pair) { + host_port_pair_ = host_port_pair; + } + + int port() const { return host_port_pair_.port(); } + const std::string& hostname() const { return host_port_pair_.host(); } + + AddressFamily address_family() const { return address_family_; } + void set_address_family(AddressFamily address_family) { + address_family_ = address_family; + } + + HostResolverFlags host_resolver_flags() const { + return host_resolver_flags_; + } + void set_host_resolver_flags(HostResolverFlags host_resolver_flags) { + host_resolver_flags_ = host_resolver_flags; + } + + bool allow_cached_response() const { return allow_cached_response_; } + void set_allow_cached_response(bool b) { allow_cached_response_ = b; } + + bool is_speculative() const { return is_speculative_; } + void set_is_speculative(bool b) { is_speculative_ = b; } + + RequestPriority priority() const { return priority_; } + void set_priority(RequestPriority priority) { priority_ = priority; } + + private: + // The hostname to resolve, and the port to use in resulting sockaddrs. + HostPortPair host_port_pair_; + + // The address family to restrict results to. + AddressFamily address_family_; + + // Flags to use when resolving this request. + HostResolverFlags host_resolver_flags_; + + // Whether it is ok to return a result from the host cache. + bool allow_cached_response_; + + // Whether this request was started by the DNS prefetcher. + bool is_speculative_; + + // The priority for the request. + RequestPriority priority_; + }; + + // Opaque type used to cancel a request. + typedef void* RequestHandle; + + // This value can be passed into CreateSystemResolver as the + // |max_concurrent_resolves| parameter. It will select a default level of + // concurrency. + static const size_t kDefaultParallelism = 0; + + // This value can be passed into CreateSystemResolver as the + // |max_retry_attempts| parameter. + static const size_t kDefaultRetryAttempts = -1; + + // If any completion callbacks are pending when the resolver is destroyed, + // the host resolutions are cancelled, and the completion callbacks will not + // be called. + virtual ~HostResolver(); + + // Resolves the given hostname (or IP address literal), filling out the + // |addresses| object upon success. The |info.port| parameter will be set as + // the sin(6)_port field of the sockaddr_in{6} struct. Returns OK if + // successful or an error code upon failure. Returns + // ERR_NAME_NOT_RESOLVED if hostname is invalid, or if it is an + // incompatible IP literal (e.g. IPv6 is disabled and it is an IPv6 + // literal). + // + // If the operation cannot be completed synchronously, ERR_IO_PENDING will + // be returned and the real result code will be passed to the completion + // callback. Otherwise the result code is returned immediately from this + // call. + // + // If |out_req| is non-NULL, then |*out_req| will be filled with a handle to + // the async request. This handle is not valid after the request has + // completed. + // + // Profiling information for the request is saved to |net_log| if non-NULL. + virtual int Resolve(const RequestInfo& info, + AddressList* addresses, + const CompletionCallback& callback, + RequestHandle* out_req, + const BoundNetLog& net_log) = 0; + + // Resolves the given hostname (or IP address literal) out of cache or HOSTS + // file (if enabled) only. This is guaranteed to complete synchronously. + // This acts like |Resolve()| if the hostname is IP literal, or cached value + // or HOSTS entry exists. Otherwise, ERR_DNS_CACHE_MISS is returned. + virtual int ResolveFromCache(const RequestInfo& info, + AddressList* addresses, + const BoundNetLog& net_log) = 0; + + // Cancels the specified request. |req| is the handle returned by Resolve(). + // After a request is canceled, its completion callback will not be called. + // CancelRequest must NOT be called after the request's completion callback + // has already run or the request was canceled. + virtual void CancelRequest(RequestHandle req) = 0; + + // Sets the default AddressFamily to use when requests have left it + // unspecified. For example, this could be used to restrict resolution + // results to AF_INET by passing in ADDRESS_FAMILY_IPV4, or to + // AF_INET6 by passing in ADDRESS_FAMILY_IPV6. + virtual void SetDefaultAddressFamily(AddressFamily address_family) {} + virtual AddressFamily GetDefaultAddressFamily() const; + + // Continuously observe whether IPv6 is supported, and set the allowable + // address family to IPv4 iff IPv6 is not supported. + virtual void ProbeIPv6Support(); + + // Enable or disable the built-in asynchronous DnsClient. + virtual void SetDnsClientEnabled(bool enabled); + + // Returns the HostResolverCache |this| uses, or NULL if there isn't one. + // Used primarily to clear the cache and for getting debug information. + virtual HostCache* GetHostCache(); + + // Returns the current DNS configuration |this| is using, as a Value, or NULL + // if it's configured to always use the system host resolver. Caller takes + // ownership of the returned Value. + virtual base::Value* GetDnsConfigAsValue() const; + + // Creates a HostResolver implementation that queries the underlying system. + // (Except if a unit-test has changed the global HostResolverProc using + // ScopedHostResolverProc to intercept requests to the system). + static scoped_ptr<HostResolver> CreateSystemResolver( + const Options& options, + NetLog* net_log); + + // As above, but uses default parameters. + static scoped_ptr<HostResolver> CreateDefaultResolver(NetLog* net_log); + + protected: + HostResolver(); + + private: + DISALLOW_COPY_AND_ASSIGN(HostResolver); +}; + +} // namespace net + +#endif // NET_DNS_HOST_RESOLVER_H_ diff --git a/net/dns/host_resolver_impl.cc b/net/dns/host_resolver_impl.cc new file mode 100644 index 0000000..91475ec --- /dev/null +++ b/net/dns/host_resolver_impl.cc @@ -0,0 +1,2180 @@ +// 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/host_resolver_impl.h" + +#if defined(OS_WIN) +#include <Winsock2.h> +#elif defined(OS_POSIX) +#include <netdb.h> +#endif + +#include <cmath> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/debug/debugger.h" +#include "base/debug/stack_trace.h" +#include "base/message_loop_proxy.h" +#include "base/metrics/field_trial.h" +#include "base/metrics/histogram.h" +#include "base/stl_util.h" +#include "base/string_util.h" +#include "base/threading/worker_pool.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "net/base/address_family.h" +#include "net/base/address_list.h" +#include "net/base/dns_reloader.h" +#include "net/base/host_port_pair.h" +#include "net/base/net_errors.h" +#include "net/base/net_log.h" +#include "net/base/net_util.h" +#include "net/dns/address_sorter.h" +#include "net/dns/dns_client.h" +#include "net/dns/dns_config_service.h" +#include "net/dns/dns_protocol.h" +#include "net/dns/dns_response.h" +#include "net/dns/dns_transaction.h" +#include "net/dns/host_resolver_proc.h" + +#if defined(OS_WIN) +#include "net/base/winsock_init.h" +#endif + +namespace net { + +namespace { + +// Limit the size of hostnames that will be resolved to combat issues in +// some platform's resolvers. +const size_t kMaxHostLength = 4096; + +// Default TTL for successful resolutions with ProcTask. +const unsigned kCacheEntryTTLSeconds = 60; + +// Default TTL for unsuccessful resolutions with ProcTask. +const unsigned kNegativeCacheEntryTTLSeconds = 0; + +// Minimum TTL for successful resolutions with DnsTask. +const unsigned kMinimumTTLSeconds = kCacheEntryTTLSeconds; + +// Number of consecutive failures of DnsTask (with successful fallback) before +// the DnsClient is disabled until the next DNS change. +const unsigned kMaximumDnsFailures = 16; + +// We use a separate histogram name for each platform to facilitate the +// display of error codes by their symbolic name (since each platform has +// different mappings). +const char kOSErrorsForGetAddrinfoHistogramName[] = +#if defined(OS_WIN) + "Net.OSErrorsForGetAddrinfo_Win"; +#elif defined(OS_MACOSX) + "Net.OSErrorsForGetAddrinfo_Mac"; +#elif defined(OS_LINUX) + "Net.OSErrorsForGetAddrinfo_Linux"; +#else + "Net.OSErrorsForGetAddrinfo"; +#endif + +// Gets a list of the likely error codes that getaddrinfo() can return +// (non-exhaustive). These are the error codes that we will track via +// a histogram. +std::vector<int> GetAllGetAddrinfoOSErrors() { + int os_errors[] = { +#if defined(OS_POSIX) +#if !defined(OS_FREEBSD) +#if !defined(OS_ANDROID) + // EAI_ADDRFAMILY has been declared obsolete in Android's and + // FreeBSD's netdb.h. + EAI_ADDRFAMILY, +#endif + // EAI_NODATA has been declared obsolete in FreeBSD's netdb.h. + EAI_NODATA, +#endif + EAI_AGAIN, + EAI_BADFLAGS, + EAI_FAIL, + EAI_FAMILY, + EAI_MEMORY, + EAI_NONAME, + EAI_SERVICE, + EAI_SOCKTYPE, + EAI_SYSTEM, +#elif defined(OS_WIN) + // See: http://msdn.microsoft.com/en-us/library/ms738520(VS.85).aspx + WSA_NOT_ENOUGH_MEMORY, + WSAEAFNOSUPPORT, + WSAEINVAL, + WSAESOCKTNOSUPPORT, + WSAHOST_NOT_FOUND, + WSANO_DATA, + WSANO_RECOVERY, + WSANOTINITIALISED, + WSATRY_AGAIN, + WSATYPE_NOT_FOUND, + // The following are not in doc, but might be to appearing in results :-(. + WSA_INVALID_HANDLE, +#endif + }; + + // Ensure all errors are positive, as histogram only tracks positive values. + for (size_t i = 0; i < arraysize(os_errors); ++i) { + os_errors[i] = std::abs(os_errors[i]); + } + + return base::CustomHistogram::ArrayToCustomRanges(os_errors, + arraysize(os_errors)); +} + +enum DnsResolveStatus { + RESOLVE_STATUS_DNS_SUCCESS = 0, + RESOLVE_STATUS_PROC_SUCCESS, + RESOLVE_STATUS_FAIL, + RESOLVE_STATUS_SUSPECT_NETBIOS, + RESOLVE_STATUS_MAX +}; + +void UmaAsyncDnsResolveStatus(DnsResolveStatus result) { + UMA_HISTOGRAM_ENUMERATION("AsyncDNS.ResolveStatus", + result, + RESOLVE_STATUS_MAX); +} + +bool ResemblesNetBIOSName(const std::string& hostname) { + return (hostname.size() < 16) && (hostname.find('.') == std::string::npos); +} + +// True if |hostname| ends with either ".local" or ".local.". +bool ResemblesMulticastDNSName(const std::string& hostname) { + DCHECK(!hostname.empty()); + const char kSuffix[] = ".local."; + const size_t kSuffixLen = sizeof(kSuffix) - 1; + const size_t kSuffixLenTrimmed = kSuffixLen - 1; + if (hostname[hostname.size() - 1] == '.') { + return hostname.size() > kSuffixLen && + !hostname.compare(hostname.size() - kSuffixLen, kSuffixLen, kSuffix); + } + return hostname.size() > kSuffixLenTrimmed && + !hostname.compare(hostname.size() - kSuffixLenTrimmed, kSuffixLenTrimmed, + kSuffix, kSuffixLenTrimmed); +} + +// Provide a common macro to simplify code and readability. We must use a +// macro as the underlying HISTOGRAM macro creates static variables. +#define DNS_HISTOGRAM(name, time) UMA_HISTOGRAM_CUSTOM_TIMES(name, time, \ + base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromHours(1), 100) + +// A macro to simplify code and readability. +#define DNS_HISTOGRAM_BY_PRIORITY(basename, priority, time) \ + do { \ + switch (priority) { \ + case HIGHEST: DNS_HISTOGRAM(basename "_HIGHEST", time); break; \ + case MEDIUM: DNS_HISTOGRAM(basename "_MEDIUM", time); break; \ + case LOW: DNS_HISTOGRAM(basename "_LOW", time); break; \ + case LOWEST: DNS_HISTOGRAM(basename "_LOWEST", time); break; \ + case IDLE: DNS_HISTOGRAM(basename "_IDLE", time); break; \ + default: NOTREACHED(); break; \ + } \ + DNS_HISTOGRAM(basename, time); \ + } while (0) + +// Record time from Request creation until a valid DNS response. +void RecordTotalTime(bool had_dns_config, + bool speculative, + base::TimeDelta duration) { + if (had_dns_config) { + if (speculative) { + DNS_HISTOGRAM("AsyncDNS.TotalTime_speculative", duration); + } else { + DNS_HISTOGRAM("AsyncDNS.TotalTime", duration); + } + } else { + if (speculative) { + DNS_HISTOGRAM("DNS.TotalTime_speculative", duration); + } else { + DNS_HISTOGRAM("DNS.TotalTime", duration); + } + } +} + +void RecordTTL(base::TimeDelta ttl) { + UMA_HISTOGRAM_CUSTOM_TIMES("AsyncDNS.TTL", ttl, + base::TimeDelta::FromSeconds(1), + base::TimeDelta::FromDays(1), 100); +} + +//----------------------------------------------------------------------------- + +// Wraps call to SystemHostResolverProc as an instance of HostResolverProc. +// TODO(szym): This should probably be declared in host_resolver_proc.h. +class CallSystemHostResolverProc : public HostResolverProc { + public: + CallSystemHostResolverProc() : HostResolverProc(NULL) {} + virtual int Resolve(const std::string& hostname, + AddressFamily address_family, + HostResolverFlags host_resolver_flags, + AddressList* addr_list, + int* os_error) OVERRIDE { + return SystemHostResolverProc(hostname, + address_family, + host_resolver_flags, + addr_list, + os_error); + } + + protected: + virtual ~CallSystemHostResolverProc() {} +}; + +AddressList EnsurePortOnAddressList(const AddressList& list, uint16 port) { + if (list.empty() || list.front().port() == port) + return list; + return AddressList::CopyWithPort(list, port); +} + +// Creates NetLog parameters when the resolve failed. +base::Value* NetLogProcTaskFailedCallback(uint32 attempt_number, + int net_error, + int os_error, + NetLog::LogLevel /* log_level */) { + DictionaryValue* dict = new DictionaryValue(); + if (attempt_number) + dict->SetInteger("attempt_number", attempt_number); + + dict->SetInteger("net_error", net_error); + + if (os_error) { + dict->SetInteger("os_error", os_error); +#if defined(OS_POSIX) + dict->SetString("os_error_string", gai_strerror(os_error)); +#elif defined(OS_WIN) + // Map the error code to a human-readable string. + LPWSTR error_string = NULL; + int size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + 0, // Use the internal message table. + os_error, + 0, // Use default language. + (LPWSTR)&error_string, + 0, // Buffer size. + 0); // Arguments (unused). + dict->SetString("os_error_string", WideToUTF8(error_string)); + LocalFree(error_string); +#endif + } + + return dict; +} + +// Creates NetLog parameters when the DnsTask failed. +base::Value* NetLogDnsTaskFailedCallback(int net_error, + int dns_error, + NetLog::LogLevel /* log_level */) { + DictionaryValue* dict = new DictionaryValue(); + dict->SetInteger("net_error", net_error); + if (dns_error) + dict->SetInteger("dns_error", dns_error); + return dict; +}; + +// Creates NetLog parameters containing the information in a RequestInfo object, +// along with the associated NetLog::Source. +base::Value* NetLogRequestInfoCallback(const NetLog::Source& source, + const HostResolver::RequestInfo* info, + NetLog::LogLevel /* log_level */) { + DictionaryValue* dict = new DictionaryValue(); + source.AddToEventParameters(dict); + + dict->SetString("host", info->host_port_pair().ToString()); + dict->SetInteger("address_family", + static_cast<int>(info->address_family())); + dict->SetBoolean("allow_cached_response", info->allow_cached_response()); + dict->SetBoolean("is_speculative", info->is_speculative()); + dict->SetInteger("priority", info->priority()); + return dict; +} + +// Creates NetLog parameters for the creation of a HostResolverImpl::Job. +base::Value* NetLogJobCreationCallback(const NetLog::Source& source, + const std::string* host, + NetLog::LogLevel /* log_level */) { + DictionaryValue* dict = new DictionaryValue(); + source.AddToEventParameters(dict); + dict->SetString("host", *host); + return dict; +} + +// Creates NetLog parameters for HOST_RESOLVER_IMPL_JOB_ATTACH/DETACH events. +base::Value* NetLogJobAttachCallback(const NetLog::Source& source, + RequestPriority priority, + NetLog::LogLevel /* log_level */) { + DictionaryValue* dict = new DictionaryValue(); + source.AddToEventParameters(dict); + dict->SetInteger("priority", priority); + return dict; +} + +// Creates NetLog parameters for the DNS_CONFIG_CHANGED event. +base::Value* NetLogDnsConfigCallback(const DnsConfig* config, + NetLog::LogLevel /* log_level */) { + return config->ToValue(); +} + +// The logging routines are defined here because some requests are resolved +// without a Request object. + +// Logs when a request has just been started. +void LogStartRequest(const BoundNetLog& source_net_log, + const BoundNetLog& request_net_log, + const HostResolver::RequestInfo& info) { + source_net_log.BeginEvent( + NetLog::TYPE_HOST_RESOLVER_IMPL, + request_net_log.source().ToEventParametersCallback()); + + request_net_log.BeginEvent( + NetLog::TYPE_HOST_RESOLVER_IMPL_REQUEST, + base::Bind(&NetLogRequestInfoCallback, source_net_log.source(), &info)); +} + +// Logs when a request has just completed (before its callback is run). +void LogFinishRequest(const BoundNetLog& source_net_log, + const BoundNetLog& request_net_log, + const HostResolver::RequestInfo& info, + int net_error) { + request_net_log.EndEventWithNetErrorCode( + NetLog::TYPE_HOST_RESOLVER_IMPL_REQUEST, net_error); + source_net_log.EndEvent(NetLog::TYPE_HOST_RESOLVER_IMPL); +} + +// Logs when a request has been cancelled. +void LogCancelRequest(const BoundNetLog& source_net_log, + const BoundNetLog& request_net_log, + const HostResolverImpl::RequestInfo& info) { + request_net_log.AddEvent(NetLog::TYPE_CANCELLED); + request_net_log.EndEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_REQUEST); + source_net_log.EndEvent(NetLog::TYPE_HOST_RESOLVER_IMPL); +} + +//----------------------------------------------------------------------------- + +// Keeps track of the highest priority. +class PriorityTracker { + public: + explicit PriorityTracker(RequestPriority initial_priority) + : highest_priority_(initial_priority), total_count_(0) { + memset(counts_, 0, sizeof(counts_)); + } + + RequestPriority highest_priority() const { + return highest_priority_; + } + + size_t total_count() const { + return total_count_; + } + + void Add(RequestPriority req_priority) { + ++total_count_; + ++counts_[req_priority]; + if (highest_priority_ < req_priority) + highest_priority_ = req_priority; + } + + void Remove(RequestPriority req_priority) { + DCHECK_GT(total_count_, 0u); + DCHECK_GT(counts_[req_priority], 0u); + --total_count_; + --counts_[req_priority]; + size_t i; + for (i = highest_priority_; i > MINIMUM_PRIORITY && !counts_[i]; --i); + highest_priority_ = static_cast<RequestPriority>(i); + + // In absence of requests, default to MINIMUM_PRIORITY. + if (total_count_ == 0) + DCHECK_EQ(MINIMUM_PRIORITY, highest_priority_); + } + + private: + RequestPriority highest_priority_; + size_t total_count_; + size_t counts_[NUM_PRIORITIES]; +}; + +} // namespace + +//----------------------------------------------------------------------------- + +// Holds the data for a request that could not be completed synchronously. +// It is owned by a Job. Canceled Requests are only marked as canceled rather +// than removed from the Job's |requests_| list. +class HostResolverImpl::Request { + public: + Request(const BoundNetLog& source_net_log, + const BoundNetLog& request_net_log, + const RequestInfo& info, + const CompletionCallback& callback, + AddressList* addresses) + : source_net_log_(source_net_log), + request_net_log_(request_net_log), + info_(info), + job_(NULL), + callback_(callback), + addresses_(addresses), + request_time_(base::TimeTicks::Now()) { + } + + // Mark the request as canceled. + void MarkAsCanceled() { + job_ = NULL; + addresses_ = NULL; + callback_.Reset(); + } + + bool was_canceled() const { + return callback_.is_null(); + } + + void set_job(Job* job) { + DCHECK(job); + // Identify which job the request is waiting on. + job_ = job; + } + + // Prepare final AddressList and call completion callback. + void OnComplete(int error, const AddressList& addr_list) { + DCHECK(!was_canceled()); + if (error == OK) + *addresses_ = EnsurePortOnAddressList(addr_list, info_.port()); + CompletionCallback callback = callback_; + MarkAsCanceled(); + callback.Run(error); + } + + Job* job() const { + return job_; + } + + // NetLog for the source, passed in HostResolver::Resolve. + const BoundNetLog& source_net_log() { + return source_net_log_; + } + + // NetLog for this request. + const BoundNetLog& request_net_log() { + return request_net_log_; + } + + const RequestInfo& info() const { + return info_; + } + + base::TimeTicks request_time() const { + return request_time_; + } + + private: + BoundNetLog source_net_log_; + BoundNetLog request_net_log_; + + // The request info that started the request. + RequestInfo info_; + + // The resolve job that this request is dependent on. + Job* job_; + + // The user's callback to invoke when the request completes. + CompletionCallback callback_; + + // The address list to save result into. + AddressList* addresses_; + + const base::TimeTicks request_time_; + + DISALLOW_COPY_AND_ASSIGN(Request); +}; + +//------------------------------------------------------------------------------ + +// Calls HostResolverProc on the WorkerPool. Performs retries if necessary. +// +// Whenever we try to resolve the host, we post a delayed task to check if host +// resolution (OnLookupComplete) is completed or not. If the original attempt +// hasn't completed, then we start another attempt for host resolution. We take +// the results from the first attempt that finishes and ignore the results from +// all other attempts. +// +// TODO(szym): Move to separate source file for testing and mocking. +// +class HostResolverImpl::ProcTask + : public base::RefCountedThreadSafe<HostResolverImpl::ProcTask> { + public: + typedef base::Callback<void(int net_error, + const AddressList& addr_list)> Callback; + + ProcTask(const Key& key, + const ProcTaskParams& params, + const Callback& callback, + const BoundNetLog& job_net_log) + : key_(key), + params_(params), + callback_(callback), + origin_loop_(base::MessageLoopProxy::current()), + attempt_number_(0), + completed_attempt_number_(0), + completed_attempt_error_(ERR_UNEXPECTED), + had_non_speculative_request_(false), + net_log_(job_net_log) { + if (!params_.resolver_proc) + params_.resolver_proc = HostResolverProc::GetDefault(); + // If default is unset, use the system proc. + if (!params_.resolver_proc) + params_.resolver_proc = new CallSystemHostResolverProc(); + } + + void Start() { + DCHECK(origin_loop_->BelongsToCurrentThread()); + net_log_.BeginEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_PROC_TASK); + StartLookupAttempt(); + } + + // Cancels this ProcTask. It will be orphaned. Any outstanding resolve + // attempts running on worker threads will continue running. Only once all the + // attempts complete will the final reference to this ProcTask be released. + void Cancel() { + DCHECK(origin_loop_->BelongsToCurrentThread()); + + if (was_canceled() || was_completed()) + return; + + callback_.Reset(); + net_log_.EndEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_PROC_TASK); + } + + void set_had_non_speculative_request() { + DCHECK(origin_loop_->BelongsToCurrentThread()); + had_non_speculative_request_ = true; + } + + bool was_canceled() const { + DCHECK(origin_loop_->BelongsToCurrentThread()); + return callback_.is_null(); + } + + bool was_completed() const { + DCHECK(origin_loop_->BelongsToCurrentThread()); + return completed_attempt_number_ > 0; + } + + private: + friend class base::RefCountedThreadSafe<ProcTask>; + ~ProcTask() {} + + void StartLookupAttempt() { + DCHECK(origin_loop_->BelongsToCurrentThread()); + base::TimeTicks start_time = base::TimeTicks::Now(); + ++attempt_number_; + // Dispatch the lookup attempt to a worker thread. + if (!base::WorkerPool::PostTask( + FROM_HERE, + base::Bind(&ProcTask::DoLookup, this, start_time, attempt_number_), + true)) { + NOTREACHED(); + + // Since we could be running within Resolve() right now, we can't just + // call OnLookupComplete(). Instead we must wait until Resolve() has + // returned (IO_PENDING). + origin_loop_->PostTask( + FROM_HERE, + base::Bind(&ProcTask::OnLookupComplete, this, AddressList(), + start_time, attempt_number_, ERR_UNEXPECTED, 0)); + return; + } + + net_log_.AddEvent( + NetLog::TYPE_HOST_RESOLVER_IMPL_ATTEMPT_STARTED, + NetLog::IntegerCallback("attempt_number", attempt_number_)); + + // If we don't get the results within a given time, RetryIfNotComplete + // will start a new attempt on a different worker thread if none of our + // outstanding attempts have completed yet. + if (attempt_number_ <= params_.max_retry_attempts) { + origin_loop_->PostDelayedTask( + FROM_HERE, + base::Bind(&ProcTask::RetryIfNotComplete, this), + params_.unresponsive_delay); + } + } + + // WARNING: This code runs inside a worker pool. The shutdown code cannot + // wait for it to finish, so we must be very careful here about using other + // objects (like MessageLoops, Singletons, etc). During shutdown these objects + // may no longer exist. Multiple DoLookups() could be running in parallel, so + // any state inside of |this| must not mutate . + void DoLookup(const base::TimeTicks& start_time, + const uint32 attempt_number) { + AddressList results; + int os_error = 0; + // Running on the worker thread + int error = params_.resolver_proc->Resolve(key_.hostname, + key_.address_family, + key_.host_resolver_flags, + &results, + &os_error); + + origin_loop_->PostTask( + FROM_HERE, + base::Bind(&ProcTask::OnLookupComplete, this, results, start_time, + attempt_number, error, os_error)); + } + + // Makes next attempt if DoLookup() has not finished (runs on origin thread). + void RetryIfNotComplete() { + DCHECK(origin_loop_->BelongsToCurrentThread()); + + if (was_completed() || was_canceled()) + return; + + params_.unresponsive_delay *= params_.retry_factor; + StartLookupAttempt(); + } + + // Callback for when DoLookup() completes (runs on origin thread). + void OnLookupComplete(const AddressList& results, + const base::TimeTicks& start_time, + const uint32 attempt_number, + int error, + const int os_error) { + DCHECK(origin_loop_->BelongsToCurrentThread()); + DCHECK(error || !results.empty()); + + bool was_retry_attempt = attempt_number > 1; + + // Ideally the following code would be part of host_resolver_proc.cc, + // however it isn't safe to call NetworkChangeNotifier from worker threads. + // So we do it here on the IO thread instead. + if (error != OK && NetworkChangeNotifier::IsOffline()) + error = ERR_INTERNET_DISCONNECTED; + + // If this is the first attempt that is finishing later, then record data + // for the first attempt. Won't contaminate with retry attempt's data. + if (!was_retry_attempt) + RecordPerformanceHistograms(start_time, error, os_error); + + RecordAttemptHistograms(start_time, attempt_number, error, os_error); + + if (was_canceled()) + return; + + NetLog::ParametersCallback net_log_callback; + if (error != OK) { + net_log_callback = base::Bind(&NetLogProcTaskFailedCallback, + attempt_number, + error, + os_error); + } else { + net_log_callback = NetLog::IntegerCallback("attempt_number", + attempt_number); + } + net_log_.AddEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_ATTEMPT_FINISHED, + net_log_callback); + + if (was_completed()) + return; + + // Copy the results from the first worker thread that resolves the host. + results_ = results; + completed_attempt_number_ = attempt_number; + completed_attempt_error_ = error; + + if (was_retry_attempt) { + // If retry attempt finishes before 1st attempt, then get stats on how + // much time is saved by having spawned an extra attempt. + retry_attempt_finished_time_ = base::TimeTicks::Now(); + } + + if (error != OK) { + net_log_callback = base::Bind(&NetLogProcTaskFailedCallback, + 0, error, os_error); + } else { + net_log_callback = results_.CreateNetLogCallback(); + } + net_log_.EndEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_PROC_TASK, + net_log_callback); + + callback_.Run(error, results_); + } + + void RecordPerformanceHistograms(const base::TimeTicks& start_time, + const int error, + const int os_error) const { + DCHECK(origin_loop_->BelongsToCurrentThread()); + enum Category { // Used in HISTOGRAM_ENUMERATION. + RESOLVE_SUCCESS, + RESOLVE_FAIL, + RESOLVE_SPECULATIVE_SUCCESS, + RESOLVE_SPECULATIVE_FAIL, + RESOLVE_MAX, // Bounding value. + }; + int category = RESOLVE_MAX; // Illegal value for later DCHECK only. + + base::TimeDelta duration = base::TimeTicks::Now() - start_time; + if (error == OK) { + if (had_non_speculative_request_) { + category = RESOLVE_SUCCESS; + DNS_HISTOGRAM("DNS.ResolveSuccess", duration); + } else { + category = RESOLVE_SPECULATIVE_SUCCESS; + DNS_HISTOGRAM("DNS.ResolveSpeculativeSuccess", duration); + } + + // Log DNS lookups based on |address_family|. This will help us determine + // if IPv4 or IPv4/6 lookups are faster or slower. + switch(key_.address_family) { + case ADDRESS_FAMILY_IPV4: + DNS_HISTOGRAM("DNS.ResolveSuccess_FAMILY_IPV4", duration); + break; + case ADDRESS_FAMILY_IPV6: + DNS_HISTOGRAM("DNS.ResolveSuccess_FAMILY_IPV6", duration); + break; + case ADDRESS_FAMILY_UNSPECIFIED: + DNS_HISTOGRAM("DNS.ResolveSuccess_FAMILY_UNSPEC", duration); + break; + } + } else { + if (had_non_speculative_request_) { + category = RESOLVE_FAIL; + DNS_HISTOGRAM("DNS.ResolveFail", duration); + } else { + category = RESOLVE_SPECULATIVE_FAIL; + DNS_HISTOGRAM("DNS.ResolveSpeculativeFail", duration); + } + // Log DNS lookups based on |address_family|. This will help us determine + // if IPv4 or IPv4/6 lookups are faster or slower. + switch(key_.address_family) { + case ADDRESS_FAMILY_IPV4: + DNS_HISTOGRAM("DNS.ResolveFail_FAMILY_IPV4", duration); + break; + case ADDRESS_FAMILY_IPV6: + DNS_HISTOGRAM("DNS.ResolveFail_FAMILY_IPV6", duration); + break; + case ADDRESS_FAMILY_UNSPECIFIED: + DNS_HISTOGRAM("DNS.ResolveFail_FAMILY_UNSPEC", duration); + break; + } + UMA_HISTOGRAM_CUSTOM_ENUMERATION(kOSErrorsForGetAddrinfoHistogramName, + std::abs(os_error), + GetAllGetAddrinfoOSErrors()); + } + DCHECK_LT(category, static_cast<int>(RESOLVE_MAX)); // Be sure it was set. + + UMA_HISTOGRAM_ENUMERATION("DNS.ResolveCategory", category, RESOLVE_MAX); + + static const bool show_parallelism_experiment_histograms = + base::FieldTrialList::TrialExists("DnsParallelism"); + if (show_parallelism_experiment_histograms) { + UMA_HISTOGRAM_ENUMERATION( + base::FieldTrial::MakeName("DNS.ResolveCategory", "DnsParallelism"), + category, RESOLVE_MAX); + if (RESOLVE_SUCCESS == category) { + DNS_HISTOGRAM(base::FieldTrial::MakeName("DNS.ResolveSuccess", + "DnsParallelism"), duration); + } + } + } + + void RecordAttemptHistograms(const base::TimeTicks& start_time, + const uint32 attempt_number, + const int error, + const int os_error) const { + DCHECK(origin_loop_->BelongsToCurrentThread()); + bool first_attempt_to_complete = + completed_attempt_number_ == attempt_number; + bool is_first_attempt = (attempt_number == 1); + + if (first_attempt_to_complete) { + // If this was first attempt to complete, then record the resolution + // status of the attempt. + if (completed_attempt_error_ == OK) { + UMA_HISTOGRAM_ENUMERATION( + "DNS.AttemptFirstSuccess", attempt_number, 100); + } else { + UMA_HISTOGRAM_ENUMERATION( + "DNS.AttemptFirstFailure", attempt_number, 100); + } + } + + if (error == OK) + UMA_HISTOGRAM_ENUMERATION("DNS.AttemptSuccess", attempt_number, 100); + else + UMA_HISTOGRAM_ENUMERATION("DNS.AttemptFailure", attempt_number, 100); + + // If first attempt didn't finish before retry attempt, then calculate stats + // on how much time is saved by having spawned an extra attempt. + if (!first_attempt_to_complete && is_first_attempt && !was_canceled()) { + DNS_HISTOGRAM("DNS.AttemptTimeSavedByRetry", + base::TimeTicks::Now() - retry_attempt_finished_time_); + } + + if (was_canceled() || !first_attempt_to_complete) { + // Count those attempts which completed after the job was already canceled + // OR after the job was already completed by an earlier attempt (so in + // effect). + UMA_HISTOGRAM_ENUMERATION("DNS.AttemptDiscarded", attempt_number, 100); + + // Record if job is canceled. + if (was_canceled()) + UMA_HISTOGRAM_ENUMERATION("DNS.AttemptCancelled", attempt_number, 100); + } + + base::TimeDelta duration = base::TimeTicks::Now() - start_time; + if (error == OK) + DNS_HISTOGRAM("DNS.AttemptSuccessDuration", duration); + else + DNS_HISTOGRAM("DNS.AttemptFailDuration", duration); + } + + // Set on the origin thread, read on the worker thread. + Key key_; + + // Holds an owning reference to the HostResolverProc that we are going to use. + // This may not be the current resolver procedure by the time we call + // ResolveAddrInfo, but that's OK... we'll use it anyways, and the owning + // reference ensures that it remains valid until we are done. + ProcTaskParams params_; + + // The listener to the results of this ProcTask. + Callback callback_; + + // Used to post ourselves onto the origin thread. + scoped_refptr<base::MessageLoopProxy> origin_loop_; + + // Keeps track of the number of attempts we have made so far to resolve the + // host. Whenever we start an attempt to resolve the host, we increase this + // number. + uint32 attempt_number_; + + // The index of the attempt which finished first (or 0 if the job is still in + // progress). + uint32 completed_attempt_number_; + + // The result (a net error code) from the first attempt to complete. + int completed_attempt_error_; + + // The time when retry attempt was finished. + base::TimeTicks retry_attempt_finished_time_; + + // True if a non-speculative request was ever attached to this job + // (regardless of whether or not it was later canceled. + // This boolean is used for histogramming the duration of jobs used to + // service non-speculative requests. + bool had_non_speculative_request_; + + AddressList results_; + + BoundNetLog net_log_; + + DISALLOW_COPY_AND_ASSIGN(ProcTask); +}; + +//----------------------------------------------------------------------------- + +// Wraps a call to TestIPv6Support to be executed on the WorkerPool as it takes +// 40-100ms. +class HostResolverImpl::IPv6ProbeJob { + public: + IPv6ProbeJob(const base::WeakPtr<HostResolverImpl>& resolver, NetLog* net_log) + : resolver_(resolver), + net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_IPV6_PROBE_JOB)), + result_(false, IPV6_SUPPORT_MAX, OK) { + DCHECK(resolver); + net_log_.BeginEvent(NetLog::TYPE_IPV6_PROBE_RUNNING); + const bool kIsSlow = true; + base::WorkerPool::PostTaskAndReply( + FROM_HERE, + base::Bind(&IPv6ProbeJob::DoProbe, base::Unretained(this)), + base::Bind(&IPv6ProbeJob::OnProbeComplete, base::Owned(this)), + kIsSlow); + } + + virtual ~IPv6ProbeJob() {} + + private: + // Runs on worker thread. + void DoProbe() { + result_ = TestIPv6Support(); + } + + void OnProbeComplete() { + net_log_.EndEvent(NetLog::TYPE_IPV6_PROBE_RUNNING, + base::Bind(&IPv6SupportResult::ToNetLogValue, + base::Unretained(&result_))); + if (!resolver_) + return; + resolver_->IPv6ProbeSetDefaultAddressFamily( + result_.ipv6_supported ? ADDRESS_FAMILY_UNSPECIFIED + : ADDRESS_FAMILY_IPV4); + } + + // Used/set only on origin thread. + base::WeakPtr<HostResolverImpl> resolver_; + + BoundNetLog net_log_; + + IPv6SupportResult result_; + + DISALLOW_COPY_AND_ASSIGN(IPv6ProbeJob); +}; + +// Wraps a call to HaveOnlyLoopbackAddresses to be executed on the WorkerPool as +// it takes 40-100ms and should not block initialization. +class HostResolverImpl::LoopbackProbeJob { + public: + explicit LoopbackProbeJob(const base::WeakPtr<HostResolverImpl>& resolver) + : resolver_(resolver), + result_(false) { + DCHECK(resolver); + const bool kIsSlow = true; + base::WorkerPool::PostTaskAndReply( + FROM_HERE, + base::Bind(&LoopbackProbeJob::DoProbe, base::Unretained(this)), + base::Bind(&LoopbackProbeJob::OnProbeComplete, base::Owned(this)), + kIsSlow); + } + + virtual ~LoopbackProbeJob() {} + + private: + // Runs on worker thread. + void DoProbe() { + result_ = HaveOnlyLoopbackAddresses(); + } + + void OnProbeComplete() { + if (!resolver_) + return; + resolver_->SetHaveOnlyLoopbackAddresses(result_); + } + + // Used/set only on origin thread. + base::WeakPtr<HostResolverImpl> resolver_; + + bool result_; + + DISALLOW_COPY_AND_ASSIGN(LoopbackProbeJob); +}; + +//----------------------------------------------------------------------------- + +// Resolves the hostname using DnsTransaction. +// TODO(szym): This could be moved to separate source file as well. +class HostResolverImpl::DnsTask : public base::SupportsWeakPtr<DnsTask> { + public: + typedef base::Callback<void(int net_error, + const AddressList& addr_list, + base::TimeDelta ttl)> Callback; + + DnsTask(DnsClient* client, + const Key& key, + const Callback& callback, + const BoundNetLog& job_net_log) + : client_(client), + family_(key.address_family), + callback_(callback), + net_log_(job_net_log) { + DCHECK(client); + DCHECK(!callback.is_null()); + + // If unspecified, do IPv4 first, because suffix search will be faster. + uint16 qtype = (family_ == ADDRESS_FAMILY_IPV6) ? + dns_protocol::kTypeAAAA : + dns_protocol::kTypeA; + transaction_ = client_->GetTransactionFactory()->CreateTransaction( + key.hostname, + qtype, + base::Bind(&DnsTask::OnTransactionComplete, base::Unretained(this), + true /* first_query */, base::TimeTicks::Now()), + net_log_); + } + + int Start() { + net_log_.BeginEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_DNS_TASK); + return transaction_->Start(); + } + + private: + void OnTransactionComplete(bool first_query, + const base::TimeTicks& start_time, + DnsTransaction* transaction, + int net_error, + const DnsResponse* response) { + DCHECK(transaction); + base::TimeDelta duration = base::TimeTicks::Now() - start_time; + // Run |callback_| last since the owning Job will then delete this DnsTask. + if (net_error != OK) { + DNS_HISTOGRAM("AsyncDNS.TransactionFailure", duration); + OnFailure(net_error, DnsResponse::DNS_PARSE_OK); + return; + } + + CHECK(response); + DNS_HISTOGRAM("AsyncDNS.TransactionSuccess", duration); + switch (transaction->GetType()) { + case dns_protocol::kTypeA: + DNS_HISTOGRAM("AsyncDNS.TransactionSuccess_A", duration); + break; + case dns_protocol::kTypeAAAA: + DNS_HISTOGRAM("AsyncDNS.TransactionSuccess_AAAA", duration); + break; + } + AddressList addr_list; + base::TimeDelta ttl; + DnsResponse::Result result = response->ParseToAddressList(&addr_list, &ttl); + UMA_HISTOGRAM_ENUMERATION("AsyncDNS.ParseToAddressList", + result, + DnsResponse::DNS_PARSE_RESULT_MAX); + if (result != DnsResponse::DNS_PARSE_OK) { + // Fail even if the other query succeeds. + OnFailure(ERR_DNS_MALFORMED_RESPONSE, result); + return; + } + + bool needs_sort = false; + if (first_query) { + DCHECK(client_->GetConfig()) << + "Transaction should have been aborted when config changed!"; + if (family_ == ADDRESS_FAMILY_IPV6) { + needs_sort = (addr_list.size() > 1); + } else if (family_ == ADDRESS_FAMILY_UNSPECIFIED) { + first_addr_list_ = addr_list; + first_ttl_ = ttl; + // Use fully-qualified domain name to avoid search. + transaction_ = client_->GetTransactionFactory()->CreateTransaction( + response->GetDottedName() + ".", + dns_protocol::kTypeAAAA, + base::Bind(&DnsTask::OnTransactionComplete, base::Unretained(this), + false /* first_query */, base::TimeTicks::Now()), + net_log_); + net_error = transaction_->Start(); + if (net_error != ERR_IO_PENDING) + OnFailure(net_error, DnsResponse::DNS_PARSE_OK); + return; + } + } else { + DCHECK_EQ(ADDRESS_FAMILY_UNSPECIFIED, family_); + bool has_ipv6_addresses = !addr_list.empty(); + if (!first_addr_list_.empty()) { + ttl = std::min(ttl, first_ttl_); + // Place IPv4 addresses after IPv6. + addr_list.insert(addr_list.end(), first_addr_list_.begin(), + first_addr_list_.end()); + } + needs_sort = (has_ipv6_addresses && addr_list.size() > 1); + } + + if (addr_list.empty()) { + // TODO(szym): Don't fallback to ProcTask in this case. + OnFailure(ERR_NAME_NOT_RESOLVED, DnsResponse::DNS_PARSE_OK); + return; + } + + if (needs_sort) { + // Sort could complete synchronously. + client_->GetAddressSorter()->Sort( + addr_list, + base::Bind(&DnsTask::OnSortComplete, + AsWeakPtr(), + base::TimeTicks::Now(), + ttl)); + } else { + OnSuccess(addr_list, ttl); + } + } + + void OnSortComplete(base::TimeTicks start_time, + base::TimeDelta ttl, + bool success, + const AddressList& addr_list) { + if (!success) { + DNS_HISTOGRAM("AsyncDNS.SortFailure", + base::TimeTicks::Now() - start_time); + OnFailure(ERR_DNS_SORT_ERROR, DnsResponse::DNS_PARSE_OK); + return; + } + + DNS_HISTOGRAM("AsyncDNS.SortSuccess", + base::TimeTicks::Now() - start_time); + + // AddressSorter prunes unusable destinations. + if (addr_list.empty()) { + LOG(WARNING) << "Address list empty after RFC3484 sort"; + OnFailure(ERR_NAME_NOT_RESOLVED, DnsResponse::DNS_PARSE_OK); + return; + } + + OnSuccess(addr_list, ttl); + } + + void OnFailure(int net_error, DnsResponse::Result result) { + DCHECK_NE(OK, net_error); + net_log_.EndEvent( + NetLog::TYPE_HOST_RESOLVER_IMPL_DNS_TASK, + base::Bind(&NetLogDnsTaskFailedCallback, net_error, result)); + callback_.Run(net_error, AddressList(), base::TimeDelta()); + } + + void OnSuccess(const AddressList& addr_list, base::TimeDelta ttl) { + net_log_.EndEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_DNS_TASK, + addr_list.CreateNetLogCallback()); + callback_.Run(OK, addr_list, ttl); + } + + DnsClient* client_; + AddressFamily family_; + // The listener to the results of this DnsTask. + Callback callback_; + const BoundNetLog net_log_; + + scoped_ptr<DnsTransaction> transaction_; + + // Results from the first transaction. Used only if |family_| is unspecified. + AddressList first_addr_list_; + base::TimeDelta first_ttl_; + + DISALLOW_COPY_AND_ASSIGN(DnsTask); +}; + +//----------------------------------------------------------------------------- + +// Aggregates all Requests for the same Key. Dispatched via PriorityDispatch. +class HostResolverImpl::Job : public PrioritizedDispatcher::Job { + public: + // Creates new job for |key| where |request_net_log| is bound to the + // request that spawned it. + Job(const base::WeakPtr<HostResolverImpl>& resolver, + const Key& key, + RequestPriority priority, + const BoundNetLog& request_net_log) + : resolver_(resolver), + key_(key), + priority_tracker_(priority), + had_non_speculative_request_(false), + had_dns_config_(false), + dns_task_error_(OK), + creation_time_(base::TimeTicks::Now()), + priority_change_time_(creation_time_), + net_log_(BoundNetLog::Make(request_net_log.net_log(), + NetLog::SOURCE_HOST_RESOLVER_IMPL_JOB)) { + request_net_log.AddEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_CREATE_JOB); + + net_log_.BeginEvent( + NetLog::TYPE_HOST_RESOLVER_IMPL_JOB, + base::Bind(&NetLogJobCreationCallback, + request_net_log.source(), + &key_.hostname)); + } + + virtual ~Job() { + if (is_running()) { + // |resolver_| was destroyed with this Job still in flight. + // Clean-up, record in the log, but don't run any callbacks. + if (is_proc_running()) { + proc_task_->Cancel(); + proc_task_ = NULL; + } + // Clean up now for nice NetLog. + dns_task_.reset(NULL); + net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HOST_RESOLVER_IMPL_JOB, + ERR_ABORTED); + } else if (is_queued()) { + // |resolver_| was destroyed without running this Job. + // TODO(szym): is there any benefit in having this distinction? + net_log_.AddEvent(NetLog::TYPE_CANCELLED); + net_log_.EndEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_JOB); + } + // else CompleteRequests logged EndEvent. + + // Log any remaining Requests as cancelled. + for (RequestsList::const_iterator it = requests_.begin(); + it != requests_.end(); ++it) { + Request* req = *it; + if (req->was_canceled()) + continue; + DCHECK_EQ(this, req->job()); + LogCancelRequest(req->source_net_log(), req->request_net_log(), + req->info()); + } + } + + // Add this job to the dispatcher. + void Schedule() { + handle_ = resolver_->dispatcher_.Add(this, priority()); + } + + void AddRequest(scoped_ptr<Request> req) { + DCHECK_EQ(key_.hostname, req->info().hostname()); + + req->set_job(this); + priority_tracker_.Add(req->info().priority()); + + req->request_net_log().AddEvent( + NetLog::TYPE_HOST_RESOLVER_IMPL_JOB_ATTACH, + net_log_.source().ToEventParametersCallback()); + + net_log_.AddEvent( + NetLog::TYPE_HOST_RESOLVER_IMPL_JOB_REQUEST_ATTACH, + base::Bind(&NetLogJobAttachCallback, + req->request_net_log().source(), + priority())); + + // TODO(szym): Check if this is still needed. + if (!req->info().is_speculative()) { + had_non_speculative_request_ = true; + if (proc_task_) + proc_task_->set_had_non_speculative_request(); + } + + requests_.push_back(req.release()); + + UpdatePriority(); + } + + // Marks |req| as cancelled. If it was the last active Request, also finishes + // this Job, marking it as cancelled, and deletes it. + void CancelRequest(Request* req) { + DCHECK_EQ(key_.hostname, req->info().hostname()); + DCHECK(!req->was_canceled()); + + // Don't remove it from |requests_| just mark it canceled. + req->MarkAsCanceled(); + LogCancelRequest(req->source_net_log(), req->request_net_log(), + req->info()); + + priority_tracker_.Remove(req->info().priority()); + net_log_.AddEvent( + NetLog::TYPE_HOST_RESOLVER_IMPL_JOB_REQUEST_DETACH, + base::Bind(&NetLogJobAttachCallback, + req->request_net_log().source(), + priority())); + + if (num_active_requests() > 0) { + UpdatePriority(); + } else { + // If we were called from a Request's callback within CompleteRequests, + // that Request could not have been cancelled, so num_active_requests() + // could not be 0. Therefore, we are not in CompleteRequests(). + CompleteRequestsWithError(OK /* cancelled */); + } + } + + // Called from AbortAllInProgressJobs. Completes all requests and destroys + // the job. This currently assumes the abort is due to a network change. + void Abort() { + DCHECK(is_running()); + CompleteRequestsWithError(ERR_NETWORK_CHANGED); + } + + // If DnsTask present, abort it and fall back to ProcTask. + void AbortDnsTask() { + if (dns_task_) { + dns_task_.reset(); + dns_task_error_ = OK; + StartProcTask(); + } + } + + // Called by HostResolverImpl when this job is evicted due to queue overflow. + // Completes all requests and destroys the job. + void OnEvicted() { + DCHECK(!is_running()); + DCHECK(is_queued()); + handle_.Reset(); + + net_log_.AddEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_JOB_EVICTED); + + // This signals to CompleteRequests that this job never ran. + CompleteRequestsWithError(ERR_HOST_RESOLVER_QUEUE_TOO_LARGE); + } + + // Attempts to serve the job from HOSTS. Returns true if succeeded and + // this Job was destroyed. + bool ServeFromHosts() { + DCHECK_GT(num_active_requests(), 0u); + AddressList addr_list; + if (resolver_->ServeFromHosts(key(), + requests_.front()->info(), + &addr_list)) { + // This will destroy the Job. + CompleteRequests( + HostCache::Entry(OK, MakeAddressListForRequest(addr_list)), + base::TimeDelta()); + return true; + } + return false; + } + + const Key key() const { + return key_; + } + + bool is_queued() const { + return !handle_.is_null(); + } + + bool is_running() const { + return is_dns_running() || is_proc_running(); + } + + private: + void UpdatePriority() { + if (is_queued()) { + if (priority() != static_cast<RequestPriority>(handle_.priority())) + priority_change_time_ = base::TimeTicks::Now(); + handle_ = resolver_->dispatcher_.ChangePriority(handle_, priority()); + } + } + + AddressList MakeAddressListForRequest(const AddressList& list) const { + if (requests_.empty()) + return list; + return AddressList::CopyWithPort(list, requests_.front()->info().port()); + } + + // PriorityDispatch::Job: + virtual void Start() OVERRIDE { + DCHECK(!is_running()); + handle_.Reset(); + + net_log_.AddEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_JOB_STARTED); + + had_dns_config_ = resolver_->HaveDnsConfig(); + + base::TimeTicks now = base::TimeTicks::Now(); + base::TimeDelta queue_time = now - creation_time_; + base::TimeDelta queue_time_after_change = now - priority_change_time_; + + if (had_dns_config_) { + DNS_HISTOGRAM_BY_PRIORITY("AsyncDNS.JobQueueTime", priority(), + queue_time); + DNS_HISTOGRAM_BY_PRIORITY("AsyncDNS.JobQueueTimeAfterChange", priority(), + queue_time_after_change); + } else { + DNS_HISTOGRAM_BY_PRIORITY("DNS.JobQueueTime", priority(), queue_time); + DNS_HISTOGRAM_BY_PRIORITY("DNS.JobQueueTimeAfterChange", priority(), + queue_time_after_change); + } + + // Caution: Job::Start must not complete synchronously. + if (had_dns_config_ && !ResemblesMulticastDNSName(key_.hostname)) { + StartDnsTask(); + } else { + StartProcTask(); + } + } + + // TODO(szym): Since DnsTransaction does not consume threads, we can increase + // the limits on |dispatcher_|. But in order to keep the number of WorkerPool + // threads low, we will need to use an "inner" PrioritizedDispatcher with + // tighter limits. + void StartProcTask() { + DCHECK(!is_dns_running()); + proc_task_ = new ProcTask( + key_, + resolver_->proc_params_, + base::Bind(&Job::OnProcTaskComplete, base::Unretained(this), + base::TimeTicks::Now()), + net_log_); + + if (had_non_speculative_request_) + proc_task_->set_had_non_speculative_request(); + // Start() could be called from within Resolve(), hence it must NOT directly + // call OnProcTaskComplete, for example, on synchronous failure. + proc_task_->Start(); + } + + // Called by ProcTask when it completes. + void OnProcTaskComplete(base::TimeTicks start_time, + int net_error, + const AddressList& addr_list) { + DCHECK(is_proc_running()); + + if (!resolver_->resolved_known_ipv6_hostname_ && + net_error == OK && + key_.address_family == ADDRESS_FAMILY_UNSPECIFIED) { + if (key_.hostname == "www.google.com") { + resolver_->resolved_known_ipv6_hostname_ = true; + bool got_ipv6_address = false; + for (size_t i = 0; i < addr_list.size(); ++i) { + if (addr_list[i].GetFamily() == ADDRESS_FAMILY_IPV6) + got_ipv6_address = true; + } + UMA_HISTOGRAM_BOOLEAN("Net.UnspecResolvedIPv6", got_ipv6_address); + } + } + + if (dns_task_error_ != OK) { + base::TimeDelta duration = base::TimeTicks::Now() - start_time; + if (net_error == OK) { + DNS_HISTOGRAM("AsyncDNS.FallbackSuccess", duration); + if ((dns_task_error_ == ERR_NAME_NOT_RESOLVED) && + ResemblesNetBIOSName(key_.hostname)) { + UmaAsyncDnsResolveStatus(RESOLVE_STATUS_SUSPECT_NETBIOS); + } else { + UmaAsyncDnsResolveStatus(RESOLVE_STATUS_PROC_SUCCESS); + } + UMA_HISTOGRAM_CUSTOM_ENUMERATION("AsyncDNS.ResolveError", + std::abs(dns_task_error_), + GetAllErrorCodesForUma()); + resolver_->OnDnsTaskResolve(dns_task_error_); + } else { + DNS_HISTOGRAM("AsyncDNS.FallbackFail", duration); + UmaAsyncDnsResolveStatus(RESOLVE_STATUS_FAIL); + } + } + + base::TimeDelta ttl = + base::TimeDelta::FromSeconds(kNegativeCacheEntryTTLSeconds); + if (net_error == OK) + ttl = base::TimeDelta::FromSeconds(kCacheEntryTTLSeconds); + + // Don't store the |ttl| in cache since it's not obtained from the server. + CompleteRequests( + HostCache::Entry(net_error, MakeAddressListForRequest(addr_list)), + ttl); + } + + void StartDnsTask() { + DCHECK(resolver_->HaveDnsConfig()); + dns_task_.reset(new DnsTask( + resolver_->dns_client_.get(), + key_, + base::Bind(&Job::OnDnsTaskComplete, base::Unretained(this), + base::TimeTicks::Now()), + net_log_)); + + int rv = dns_task_->Start(); + if (rv != ERR_IO_PENDING) { + DCHECK_NE(OK, rv); + dns_task_error_ = rv; + dns_task_.reset(); + StartProcTask(); + } + } + + // Called by DnsTask when it completes. + void OnDnsTaskComplete(base::TimeTicks start_time, + int net_error, + const AddressList& addr_list, + base::TimeDelta ttl) { + DCHECK(is_dns_running()); + + base::TimeDelta duration = base::TimeTicks::Now() - start_time; + if (net_error != OK) { + DNS_HISTOGRAM("AsyncDNS.ResolveFail", duration); + + dns_task_error_ = net_error; + dns_task_.reset(); + + // TODO(szym): Run ServeFromHosts now if nsswitch.conf says so. + // http://crbug.com/117655 + + // TODO(szym): Some net errors indicate lack of connectivity. Starting + // ProcTask in that case is a waste of time. + StartProcTask(); + return; + } + DNS_HISTOGRAM("AsyncDNS.ResolveSuccess", duration); + // Log DNS lookups based on |address_family|. + switch(key_.address_family) { + case ADDRESS_FAMILY_IPV4: + DNS_HISTOGRAM("AsyncDNS.ResolveSuccess_FAMILY_IPV4", duration); + break; + case ADDRESS_FAMILY_IPV6: + DNS_HISTOGRAM("AsyncDNS.ResolveSuccess_FAMILY_IPV6", duration); + break; + case ADDRESS_FAMILY_UNSPECIFIED: + DNS_HISTOGRAM("AsyncDNS.ResolveSuccess_FAMILY_UNSPEC", duration); + break; + } + + UmaAsyncDnsResolveStatus(RESOLVE_STATUS_DNS_SUCCESS); + RecordTTL(ttl); + + resolver_->OnDnsTaskResolve(OK); + + base::TimeDelta bounded_ttl = + std::max(ttl, base::TimeDelta::FromSeconds(kMinimumTTLSeconds)); + + CompleteRequests( + HostCache::Entry(net_error, MakeAddressListForRequest(addr_list), ttl), + bounded_ttl); + } + + // Performs Job's last rites. Completes all Requests. Deletes this. + void CompleteRequests(const HostCache::Entry& entry, + base::TimeDelta ttl) { + CHECK(resolver_); + + // This job must be removed from resolver's |jobs_| now to make room for a + // new job with the same key in case one of the OnComplete callbacks decides + // to spawn one. Consequently, the job deletes itself when CompleteRequests + // is done. + scoped_ptr<Job> self_deleter(this); + + resolver_->RemoveJob(this); + + if (is_running()) { + DCHECK(!is_queued()); + if (is_proc_running()) { + proc_task_->Cancel(); + proc_task_ = NULL; + } + dns_task_.reset(); + + // Signal dispatcher that a slot has opened. + resolver_->dispatcher_.OnJobFinished(); + } else if (is_queued()) { + resolver_->dispatcher_.Cancel(handle_); + handle_.Reset(); + } + + if (num_active_requests() == 0) { + net_log_.AddEvent(NetLog::TYPE_CANCELLED); + net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HOST_RESOLVER_IMPL_JOB, + OK); + return; + } + + net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HOST_RESOLVER_IMPL_JOB, + entry.error); + + DCHECK(!requests_.empty()); + + if (entry.error == OK) { + // Record this histogram here, when we know the system has a valid DNS + // configuration. + UMA_HISTOGRAM_BOOLEAN("AsyncDNS.HaveDnsConfig", + resolver_->received_dns_config_); + } + + bool did_complete = (entry.error != ERR_NETWORK_CHANGED) && + (entry.error != ERR_HOST_RESOLVER_QUEUE_TOO_LARGE); + if (did_complete) + resolver_->CacheResult(key_, entry, ttl); + + // Complete all of the requests that were attached to the job. + for (RequestsList::const_iterator it = requests_.begin(); + it != requests_.end(); ++it) { + Request* req = *it; + + if (req->was_canceled()) + continue; + + DCHECK_EQ(this, req->job()); + // Update the net log and notify registered observers. + LogFinishRequest(req->source_net_log(), req->request_net_log(), + req->info(), entry.error); + if (did_complete) { + // Record effective total time from creation to completion. + RecordTotalTime(had_dns_config_, req->info().is_speculative(), + base::TimeTicks::Now() - req->request_time()); + } + req->OnComplete(entry.error, entry.addrlist); + + // Check if the resolver was destroyed as a result of running the + // callback. If it was, we could continue, but we choose to bail. + if (!resolver_) + return; + } + } + + // Convenience wrapper for CompleteRequests in case of failure. + void CompleteRequestsWithError(int net_error) { + CompleteRequests(HostCache::Entry(net_error, AddressList()), + base::TimeDelta()); + } + + RequestPriority priority() const { + return priority_tracker_.highest_priority(); + } + + // Number of non-canceled requests in |requests_|. + size_t num_active_requests() const { + return priority_tracker_.total_count(); + } + + bool is_dns_running() const { + return dns_task_.get() != NULL; + } + + bool is_proc_running() const { + return proc_task_.get() != NULL; + } + + base::WeakPtr<HostResolverImpl> resolver_; + + Key key_; + + // Tracks the highest priority across |requests_|. + PriorityTracker priority_tracker_; + + bool had_non_speculative_request_; + + // Distinguishes measurements taken while DnsClient was fully configured. + bool had_dns_config_; + + // Result of DnsTask. + int dns_task_error_; + + const base::TimeTicks creation_time_; + base::TimeTicks priority_change_time_; + + BoundNetLog net_log_; + + // Resolves the host using a HostResolverProc. + scoped_refptr<ProcTask> proc_task_; + + // Resolves the host using a DnsTransaction. + scoped_ptr<DnsTask> dns_task_; + + // All Requests waiting for the result of this Job. Some can be canceled. + RequestsList requests_; + + // A handle used in |HostResolverImpl::dispatcher_|. + PrioritizedDispatcher::Handle handle_; +}; + +//----------------------------------------------------------------------------- + +HostResolverImpl::ProcTaskParams::ProcTaskParams( + HostResolverProc* resolver_proc, + size_t max_retry_attempts) + : resolver_proc(resolver_proc), + max_retry_attempts(max_retry_attempts), + unresponsive_delay(base::TimeDelta::FromMilliseconds(6000)), + retry_factor(2) { +} + +HostResolverImpl::ProcTaskParams::~ProcTaskParams() {} + +HostResolverImpl::HostResolverImpl( + scoped_ptr<HostCache> cache, + const PrioritizedDispatcher::Limits& job_limits, + const ProcTaskParams& proc_params, + NetLog* net_log) + : cache_(cache.Pass()), + dispatcher_(job_limits), + max_queued_jobs_(job_limits.total_jobs * 100u), + proc_params_(proc_params), + net_log_(net_log), + default_address_family_(ADDRESS_FAMILY_UNSPECIFIED), + weak_ptr_factory_(this), + probe_weak_ptr_factory_(this), + received_dns_config_(false), + num_dns_failures_(0), + ipv6_probe_monitoring_(false), + resolved_known_ipv6_hostname_(false), + additional_resolver_flags_(0) { + + DCHECK_GE(dispatcher_.num_priorities(), static_cast<size_t>(NUM_PRIORITIES)); + + // Maximum of 4 retry attempts for host resolution. + static const size_t kDefaultMaxRetryAttempts = 4u; + + if (proc_params_.max_retry_attempts == HostResolver::kDefaultRetryAttempts) + proc_params_.max_retry_attempts = kDefaultMaxRetryAttempts; + +#if defined(OS_WIN) + EnsureWinsockInit(); +#endif +#if defined(OS_POSIX) && !defined(OS_MACOSX) + new LoopbackProbeJob(weak_ptr_factory_.GetWeakPtr()); +#endif + NetworkChangeNotifier::AddIPAddressObserver(this); + NetworkChangeNotifier::AddDNSObserver(this); +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD) && \ + !defined(OS_ANDROID) + EnsureDnsReloaderInit(); +#endif + + // TODO(szym): Remove when received_dns_config_ is removed, once + // http://crbug.com/137914 is resolved. + { + DnsConfig dns_config; + NetworkChangeNotifier::GetDnsConfig(&dns_config); + received_dns_config_ = dns_config.IsValid(); + } +} + +HostResolverImpl::~HostResolverImpl() { + // This will also cancel all outstanding requests. + STLDeleteValues(&jobs_); + + NetworkChangeNotifier::RemoveIPAddressObserver(this); + NetworkChangeNotifier::RemoveDNSObserver(this); +} + +void HostResolverImpl::SetMaxQueuedJobs(size_t value) { + DCHECK_EQ(0u, dispatcher_.num_queued_jobs()); + DCHECK_GT(value, 0u); + max_queued_jobs_ = value; +} + +int HostResolverImpl::Resolve(const RequestInfo& info, + AddressList* addresses, + const CompletionCallback& callback, + RequestHandle* out_req, + const BoundNetLog& source_net_log) { + DCHECK(addresses); + DCHECK(CalledOnValidThread()); + DCHECK_EQ(false, callback.is_null()); + + // Make a log item for the request. + BoundNetLog request_net_log = BoundNetLog::Make(net_log_, + NetLog::SOURCE_HOST_RESOLVER_IMPL_REQUEST); + + LogStartRequest(source_net_log, request_net_log, info); + + // Build a key that identifies the request in the cache and in the + // outstanding jobs map. + Key key = GetEffectiveKeyForRequest(info); + + int rv = ResolveHelper(key, info, addresses, request_net_log); + if (rv != ERR_DNS_CACHE_MISS) { + LogFinishRequest(source_net_log, request_net_log, info, rv); + RecordTotalTime(HaveDnsConfig(), info.is_speculative(), base::TimeDelta()); + return rv; + } + + // Next we need to attach our request to a "job". This job is responsible for + // calling "getaddrinfo(hostname)" on a worker thread. + + JobMap::iterator jobit = jobs_.find(key); + Job* job; + if (jobit == jobs_.end()) { + job = new Job(weak_ptr_factory_.GetWeakPtr(), key, info.priority(), + request_net_log); + job->Schedule(); + + // Check for queue overflow. + if (dispatcher_.num_queued_jobs() > max_queued_jobs_) { + Job* evicted = static_cast<Job*>(dispatcher_.EvictOldestLowest()); + DCHECK(evicted); + evicted->OnEvicted(); // Deletes |evicted|. + if (evicted == job) { + rv = ERR_HOST_RESOLVER_QUEUE_TOO_LARGE; + LogFinishRequest(source_net_log, request_net_log, info, rv); + return rv; + } + } + jobs_.insert(jobit, std::make_pair(key, job)); + } else { + job = jobit->second; + } + + // Can't complete synchronously. Create and attach request. + scoped_ptr<Request> req(new Request(source_net_log, + request_net_log, + info, + callback, + addresses)); + if (out_req) + *out_req = reinterpret_cast<RequestHandle>(req.get()); + + job->AddRequest(req.Pass()); + // Completion happens during Job::CompleteRequests(). + return ERR_IO_PENDING; +} + +int HostResolverImpl::ResolveHelper(const Key& key, + const RequestInfo& info, + AddressList* addresses, + const BoundNetLog& request_net_log) { + // The result of |getaddrinfo| for empty hosts is inconsistent across systems. + // On Windows it gives the default interface's address, whereas on Linux it + // gives an error. We will make it fail on all platforms for consistency. + if (info.hostname().empty() || info.hostname().size() > kMaxHostLength) + return ERR_NAME_NOT_RESOLVED; + + int net_error = ERR_UNEXPECTED; + if (ResolveAsIP(key, info, &net_error, addresses)) + return net_error; + if (ServeFromCache(key, info, &net_error, addresses)) { + request_net_log.AddEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_CACHE_HIT); + return net_error; + } + // TODO(szym): Do not do this if nsswitch.conf instructs not to. + // http://crbug.com/117655 + if (ServeFromHosts(key, info, addresses)) { + request_net_log.AddEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_HOSTS_HIT); + return OK; + } + return ERR_DNS_CACHE_MISS; +} + +int HostResolverImpl::ResolveFromCache(const RequestInfo& info, + AddressList* addresses, + const BoundNetLog& source_net_log) { + DCHECK(CalledOnValidThread()); + DCHECK(addresses); + + // Make a log item for the request. + BoundNetLog request_net_log = BoundNetLog::Make(net_log_, + NetLog::SOURCE_HOST_RESOLVER_IMPL_REQUEST); + + // Update the net log and notify registered observers. + LogStartRequest(source_net_log, request_net_log, info); + + Key key = GetEffectiveKeyForRequest(info); + + int rv = ResolveHelper(key, info, addresses, request_net_log); + LogFinishRequest(source_net_log, request_net_log, info, rv); + return rv; +} + +void HostResolverImpl::CancelRequest(RequestHandle req_handle) { + DCHECK(CalledOnValidThread()); + Request* req = reinterpret_cast<Request*>(req_handle); + DCHECK(req); + Job* job = req->job(); + DCHECK(job); + job->CancelRequest(req); +} + +void HostResolverImpl::SetDefaultAddressFamily(AddressFamily address_family) { + DCHECK(CalledOnValidThread()); + default_address_family_ = address_family; + ipv6_probe_monitoring_ = false; +} + +AddressFamily HostResolverImpl::GetDefaultAddressFamily() const { + return default_address_family_; +} + +void HostResolverImpl::ProbeIPv6Support() { + DCHECK(CalledOnValidThread()); + DCHECK(!ipv6_probe_monitoring_); + ipv6_probe_monitoring_ = true; + OnIPAddressChanged(); +} + +void HostResolverImpl::SetDnsClientEnabled(bool enabled) { + DCHECK(CalledOnValidThread()); +#if defined(ENABLE_BUILT_IN_DNS) + if (enabled && !dns_client_) { + SetDnsClient(DnsClient::CreateClient(net_log_)); + } else if (!enabled && dns_client_) { + SetDnsClient(scoped_ptr<DnsClient>()); + } +#endif +} + +HostCache* HostResolverImpl::GetHostCache() { + return cache_.get(); +} + +base::Value* HostResolverImpl::GetDnsConfigAsValue() const { + // Check if async DNS is disabled. + if (!dns_client_.get()) + return NULL; + + // Check if async DNS is enabled, but we currently have no configuration + // for it. + const DnsConfig* dns_config = dns_client_->GetConfig(); + if (dns_config == NULL) + return new DictionaryValue(); + + return dns_config->ToValue(); +} + +bool HostResolverImpl::ResolveAsIP(const Key& key, + const RequestInfo& info, + int* net_error, + AddressList* addresses) { + DCHECK(addresses); + DCHECK(net_error); + IPAddressNumber ip_number; + if (!ParseIPLiteralToNumber(key.hostname, &ip_number)) + return false; + + DCHECK_EQ(key.host_resolver_flags & + ~(HOST_RESOLVER_CANONNAME | HOST_RESOLVER_LOOPBACK_ONLY | + HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6), + 0) << " Unhandled flag"; + bool ipv6_disabled = (default_address_family_ == ADDRESS_FAMILY_IPV4) && + !ipv6_probe_monitoring_; + *net_error = OK; + if ((ip_number.size() == kIPv6AddressSize) && ipv6_disabled) { + *net_error = ERR_NAME_NOT_RESOLVED; + } else { + *addresses = AddressList::CreateFromIPAddress(ip_number, info.port()); + if (key.host_resolver_flags & HOST_RESOLVER_CANONNAME) + addresses->SetDefaultCanonicalName(); + } + return true; +} + +bool HostResolverImpl::ServeFromCache(const Key& key, + const RequestInfo& info, + int* net_error, + AddressList* addresses) { + DCHECK(addresses); + DCHECK(net_error); + if (!info.allow_cached_response() || !cache_.get()) + return false; + + const HostCache::Entry* cache_entry = cache_->Lookup( + key, base::TimeTicks::Now()); + if (!cache_entry) + return false; + + *net_error = cache_entry->error; + if (*net_error == OK) { + if (cache_entry->has_ttl()) + RecordTTL(cache_entry->ttl); + *addresses = EnsurePortOnAddressList(cache_entry->addrlist, info.port()); + } + return true; +} + +bool HostResolverImpl::ServeFromHosts(const Key& key, + const RequestInfo& info, + AddressList* addresses) { + DCHECK(addresses); + if (!HaveDnsConfig()) + return false; + + // HOSTS lookups are case-insensitive. + std::string hostname = StringToLowerASCII(key.hostname); + + // If |address_family| is ADDRESS_FAMILY_UNSPECIFIED other implementations + // (glibc and c-ares) return the first matching line. We have more + // flexibility, but lose implicit ordering. + // TODO(szym) http://crbug.com/117850 + const DnsHosts& hosts = dns_client_->GetConfig()->hosts; + DnsHosts::const_iterator it = hosts.find( + DnsHostsKey(hostname, + key.address_family == ADDRESS_FAMILY_UNSPECIFIED ? + ADDRESS_FAMILY_IPV4 : key.address_family)); + + if (it == hosts.end()) { + if (key.address_family != ADDRESS_FAMILY_UNSPECIFIED) + return false; + + it = hosts.find(DnsHostsKey(hostname, ADDRESS_FAMILY_IPV6)); + if (it == hosts.end()) + return false; + } + + *addresses = AddressList::CreateFromIPAddress(it->second, info.port()); + return true; +} + +void HostResolverImpl::CacheResult(const Key& key, + const HostCache::Entry& entry, + base::TimeDelta ttl) { + if (cache_.get()) + cache_->Set(key, entry, base::TimeTicks::Now(), ttl); +} + +void HostResolverImpl::RemoveJob(Job* job) { + DCHECK(job); + JobMap::iterator it = jobs_.find(job->key()); + if (it != jobs_.end() && it->second == job) + jobs_.erase(it); +} + +void HostResolverImpl::IPv6ProbeSetDefaultAddressFamily( + AddressFamily address_family) { + DCHECK(address_family == ADDRESS_FAMILY_UNSPECIFIED || + address_family == ADDRESS_FAMILY_IPV4); + if (!ipv6_probe_monitoring_) + return; + if (default_address_family_ != address_family) { + VLOG(1) << "IPv6Probe forced AddressFamily setting to " + << ((address_family == ADDRESS_FAMILY_UNSPECIFIED) ? + "ADDRESS_FAMILY_UNSPECIFIED" : "ADDRESS_FAMILY_IPV4"); + } + default_address_family_ = address_family; +} + +void HostResolverImpl::SetHaveOnlyLoopbackAddresses(bool result) { + if (result) { + additional_resolver_flags_ |= HOST_RESOLVER_LOOPBACK_ONLY; + } else { + additional_resolver_flags_ &= ~HOST_RESOLVER_LOOPBACK_ONLY; + } +} + +HostResolverImpl::Key HostResolverImpl::GetEffectiveKeyForRequest( + const RequestInfo& info) const { + HostResolverFlags effective_flags = + info.host_resolver_flags() | additional_resolver_flags_; + AddressFamily effective_address_family = info.address_family(); + if (effective_address_family == ADDRESS_FAMILY_UNSPECIFIED && + default_address_family_ != ADDRESS_FAMILY_UNSPECIFIED) { + effective_address_family = default_address_family_; + if (ipv6_probe_monitoring_) + effective_flags |= HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6; + } + return Key(info.hostname(), effective_address_family, effective_flags); +} + +void HostResolverImpl::AbortAllInProgressJobs() { + // In Abort, a Request callback could spawn new Jobs with matching keys, so + // first collect and remove all running jobs from |jobs_|. + ScopedVector<Job> jobs_to_abort; + for (JobMap::iterator it = jobs_.begin(); it != jobs_.end(); ) { + Job* job = it->second; + if (job->is_running()) { + jobs_to_abort.push_back(job); + jobs_.erase(it++); + } else { + DCHECK(job->is_queued()); + ++it; + } + } + + // Check if no dispatcher slots leaked out. + DCHECK_EQ(dispatcher_.num_running_jobs(), jobs_to_abort.size()); + + // Life check to bail once |this| is deleted. + base::WeakPtr<HostResolverImpl> self = weak_ptr_factory_.GetWeakPtr(); + + // Then Abort them. + for (size_t i = 0; self && i < jobs_to_abort.size(); ++i) { + jobs_to_abort[i]->Abort(); + jobs_to_abort[i] = NULL; + } +} + +void HostResolverImpl::TryServingAllJobsFromHosts() { + if (!HaveDnsConfig()) + return; + + // TODO(szym): Do not do this if nsswitch.conf instructs not to. + // http://crbug.com/117655 + + // Life check to bail once |this| is deleted. + base::WeakPtr<HostResolverImpl> self = weak_ptr_factory_.GetWeakPtr(); + + for (JobMap::iterator it = jobs_.begin(); self && it != jobs_.end(); ) { + Job* job = it->second; + ++it; + // This could remove |job| from |jobs_|, but iterator will remain valid. + job->ServeFromHosts(); + } +} + +void HostResolverImpl::OnIPAddressChanged() { + resolved_known_ipv6_hostname_ = false; + // Abandon all ProbeJobs. + probe_weak_ptr_factory_.InvalidateWeakPtrs(); + if (cache_.get()) + cache_->clear(); + if (ipv6_probe_monitoring_) + new IPv6ProbeJob(probe_weak_ptr_factory_.GetWeakPtr(), net_log_); +#if defined(OS_POSIX) && !defined(OS_MACOSX) + new LoopbackProbeJob(probe_weak_ptr_factory_.GetWeakPtr()); +#endif + AbortAllInProgressJobs(); + // |this| may be deleted inside AbortAllInProgressJobs(). +} + +void HostResolverImpl::OnDNSChanged() { + DnsConfig dns_config; + NetworkChangeNotifier::GetDnsConfig(&dns_config); + if (net_log_) { + net_log_->AddGlobalEntry( + NetLog::TYPE_DNS_CONFIG_CHANGED, + base::Bind(&NetLogDnsConfigCallback, &dns_config)); + } + + // TODO(szym): Remove once http://crbug.com/137914 is resolved. + received_dns_config_ = dns_config.IsValid(); + + num_dns_failures_ = 0; + + // We want a new DnsSession in place, before we Abort running Jobs, so that + // the newly started jobs use the new config. + if (dns_client_.get()) { + dns_client_->SetConfig(dns_config); + if (dns_config.IsValid()) + UMA_HISTOGRAM_BOOLEAN("AsyncDNS.DnsClientEnabled", true); + } + + // If the DNS server has changed, existing cached info could be wrong so we + // have to drop our internal cache :( Note that OS level DNS caches, such + // as NSCD's cache should be dropped automatically by the OS when + // resolv.conf changes so we don't need to do anything to clear that cache. + if (cache_.get()) + cache_->clear(); + + // Life check to bail once |this| is deleted. + base::WeakPtr<HostResolverImpl> self = weak_ptr_factory_.GetWeakPtr(); + + // Existing jobs will have been sent to the original server so they need to + // be aborted. + AbortAllInProgressJobs(); + + // |this| may be deleted inside AbortAllInProgressJobs(). + if (self) + TryServingAllJobsFromHosts(); +} + +bool HostResolverImpl::HaveDnsConfig() const { + // Use DnsClient only if it's fully configured and there is no override by + // ScopedDefaultHostResolverProc. + // The alternative is to use NetworkChangeNotifier to override DnsConfig, + // but that would introduce construction order requirements for NCN and SDHRP. + return (dns_client_.get() != NULL) && + (dns_client_->GetConfig() != NULL) && + !(proc_params_.resolver_proc == NULL && + HostResolverProc::GetDefault() != NULL); +} + +void HostResolverImpl::OnDnsTaskResolve(int net_error) { + DCHECK(dns_client_); + if (net_error == OK) { + num_dns_failures_ = 0; + return; + } + ++num_dns_failures_; + if (num_dns_failures_ < kMaximumDnsFailures) + return; + // Disable DnsClient until the next DNS change. + for (JobMap::iterator it = jobs_.begin(); it != jobs_.end(); ++it) + it->second->AbortDnsTask(); + dns_client_->SetConfig(DnsConfig()); + UMA_HISTOGRAM_BOOLEAN("AsyncDNS.DnsClientEnabled", false); + UMA_HISTOGRAM_CUSTOM_ENUMERATION("AsyncDNS.DnsClientDisabledReason", + std::abs(net_error), + GetAllErrorCodesForUma()); +} + +void HostResolverImpl::SetDnsClient(scoped_ptr<DnsClient> dns_client) { + if (HaveDnsConfig()) { + for (JobMap::iterator it = jobs_.begin(); it != jobs_.end(); ++it) + it->second->AbortDnsTask(); + } + dns_client_ = dns_client.Pass(); + if (!dns_client_ || dns_client_->GetConfig() || + num_dns_failures_ >= kMaximumDnsFailures) { + return; + } + DnsConfig dns_config; + NetworkChangeNotifier::GetDnsConfig(&dns_config); + dns_client_->SetConfig(dns_config); + num_dns_failures_ = 0; + if (dns_config.IsValid()) + UMA_HISTOGRAM_BOOLEAN("AsyncDNS.DnsClientEnabled", true); +} + +} // namespace net diff --git a/net/dns/host_resolver_impl.h b/net/dns/host_resolver_impl.h new file mode 100644 index 0000000..2519d13 --- /dev/null +++ b/net/dns/host_resolver_impl.h @@ -0,0 +1,287 @@ +// 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. + +#ifndef NET_DNS_HOST_RESOLVER_IMPL_H_ +#define NET_DNS_HOST_RESOLVER_IMPL_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "base/time.h" +#include "net/base/capturing_net_log.h" +#include "net/base/host_cache.h" +#include "net/base/net_export.h" +#include "net/base/network_change_notifier.h" +#include "net/base/prioritized_dispatcher.h" +#include "net/dns/host_resolver.h" +#include "net/dns/host_resolver_proc.h" + +namespace net { + +class BoundNetLog; +class DnsClient; +class NetLog; + +// For each hostname that is requested, HostResolver creates a +// HostResolverImpl::Job. When this job gets dispatched it creates a ProcTask +// which runs the given HostResolverProc on a WorkerPool thread. If requests for +// that same host are made during the job's lifetime, they are attached to the +// existing job rather than creating a new one. This avoids doing parallel +// resolves for the same host. +// +// The way these classes fit together is illustrated by: +// +// +// +----------- HostResolverImpl -------------+ +// | | | +// Job Job Job +// (for host1, fam1) (for host2, fam2) (for hostx, famx) +// / | | / | | / | | +// Request ... Request Request ... Request Request ... Request +// (port1) (port2) (port3) (port4) (port5) (portX) +// +// When a HostResolverImpl::Job finishes, the callbacks of each waiting request +// are run on the origin thread. +// +// Thread safety: This class is not threadsafe, and must only be called +// from one thread! +// +// The HostResolverImpl enforces limits on the maximum number of concurrent +// threads using PrioritizedDispatcher::Limits. +// +// Jobs are ordered in the queue based on their priority and order of arrival. +class NET_EXPORT HostResolverImpl + : public HostResolver, + NON_EXPORTED_BASE(public base::NonThreadSafe), + public NetworkChangeNotifier::IPAddressObserver, + public NetworkChangeNotifier::DNSObserver { + public: + // Parameters for ProcTask which resolves hostnames using HostResolveProc. + // + // |resolver_proc| is used to perform the actual resolves; it must be + // thread-safe since it is run from multiple worker threads. If + // |resolver_proc| is NULL then the default host resolver procedure is + // used (which is SystemHostResolverProc except if overridden). + // + // For each attempt, we could start another attempt if host is not resolved + // within |unresponsive_delay| time. We keep attempting to resolve the host + // for |max_retry_attempts|. For every retry attempt, we grow the + // |unresponsive_delay| by the |retry_factor| amount (that is retry interval + // is multiplied by the retry factor each time). Once we have retried + // |max_retry_attempts|, we give up on additional attempts. + // + struct NET_EXPORT_PRIVATE ProcTaskParams { + // Sets up defaults. + ProcTaskParams(HostResolverProc* resolver_proc, size_t max_retry_attempts); + + ~ProcTaskParams(); + + // The procedure to use for resolving host names. This will be NULL, except + // in the case of unit-tests which inject custom host resolving behaviors. + scoped_refptr<HostResolverProc> resolver_proc; + + // Maximum number retry attempts to resolve the hostname. + // Pass HostResolver::kDefaultRetryAttempts to choose a default value. + size_t max_retry_attempts; + + // This is the limit after which we make another attempt to resolve the host + // if the worker thread has not responded yet. + base::TimeDelta unresponsive_delay; + + // Factor to grow |unresponsive_delay| when we re-re-try. + uint32 retry_factor; + }; + + // Creates a HostResolver that first uses the local cache |cache|, and then + // falls back to |proc_params.resolver_proc|. + // + // If |cache| is NULL, then no caching is used. Otherwise we take + // ownership of the |cache| pointer, and will free it during destruction. + // + // |job_limits| specifies the maximum number of jobs that the resolver will + // run at once. This upper-bounds the total number of outstanding + // DNS transactions (not counting retransmissions and retries). + // + // |net_log| must remain valid for the life of the HostResolverImpl. + HostResolverImpl(scoped_ptr<HostCache> cache, + const PrioritizedDispatcher::Limits& job_limits, + const ProcTaskParams& proc_params, + NetLog* net_log); + + // If any completion callbacks are pending when the resolver is destroyed, + // the host resolutions are cancelled, and the completion callbacks will not + // be called. + virtual ~HostResolverImpl(); + + // Configures maximum number of Jobs in the queue. Exposed for testing. + // Only allowed when the queue is empty. + void SetMaxQueuedJobs(size_t value); + + // Set the DnsClient to be used for resolution. In case of failure, the + // HostResolverProc from ProcTaskParams will be queried. If the DnsClient is + // not pre-configured with a valid DnsConfig, a new config is fetched from + // NetworkChangeNotifier. + void SetDnsClient(scoped_ptr<DnsClient> dns_client); + + // HostResolver methods: + virtual int Resolve(const RequestInfo& info, + AddressList* addresses, + const CompletionCallback& callback, + RequestHandle* out_req, + const BoundNetLog& source_net_log) OVERRIDE; + virtual int ResolveFromCache(const RequestInfo& info, + AddressList* addresses, + const BoundNetLog& source_net_log) OVERRIDE; + virtual void CancelRequest(RequestHandle req) OVERRIDE; + virtual void SetDefaultAddressFamily(AddressFamily address_family) OVERRIDE; + virtual AddressFamily GetDefaultAddressFamily() const OVERRIDE; + virtual void ProbeIPv6Support() OVERRIDE; + virtual void SetDnsClientEnabled(bool enabled) OVERRIDE; + virtual HostCache* GetHostCache() OVERRIDE; + virtual base::Value* GetDnsConfigAsValue() const OVERRIDE; + + private: + friend class HostResolverImplTest; + class Job; + class ProcTask; + class IPv6ProbeJob; + class LoopbackProbeJob; + class DnsTask; + class Request; + typedef HostCache::Key Key; + typedef std::map<Key, Job*> JobMap; + typedef ScopedVector<Request> RequestsList; + + // Helper used by |Resolve()| and |ResolveFromCache()|. Performs IP + // literal, cache and HOSTS lookup (if enabled), returns OK if successful, + // ERR_NAME_NOT_RESOLVED if either hostname is invalid or IP literal is + // incompatible, ERR_DNS_CACHE_MISS if entry was not found in cache and HOSTS. + int ResolveHelper(const Key& key, + const RequestInfo& info, + AddressList* addresses, + const BoundNetLog& request_net_log); + + // Tries to resolve |key| as an IP, returns true and sets |net_error| if + // succeeds, returns false otherwise. + bool ResolveAsIP(const Key& key, + const RequestInfo& info, + int* net_error, + AddressList* addresses); + + // If |key| is not found in cache returns false, otherwise returns + // true, sets |net_error| to the cached error code and fills |addresses| + // if it is a positive entry. + bool ServeFromCache(const Key& key, + const RequestInfo& info, + int* net_error, + AddressList* addresses); + + // If we have a DnsClient with a valid DnsConfig, and |key| is found in the + // HOSTS file, returns true and fills |addresses|. Otherwise returns false. + bool ServeFromHosts(const Key& key, + const RequestInfo& info, + AddressList* addresses); + + // Callback from IPv6 probe activity. + void IPv6ProbeSetDefaultAddressFamily(AddressFamily address_family); + + // Callback from HaveOnlyLoopbackAddresses probe. + void SetHaveOnlyLoopbackAddresses(bool result); + + // Returns the (hostname, address_family) key to use for |info|, choosing an + // "effective" address family by inheriting the resolver's default address + // family when the request leaves it unspecified. + Key GetEffectiveKeyForRequest(const RequestInfo& info) const; + + // Records the result in cache if cache is present. + void CacheResult(const Key& key, + const HostCache::Entry& entry, + base::TimeDelta ttl); + + // Removes |job| from |jobs_|, only if it exists. + void RemoveJob(Job* job); + + // Aborts all in progress jobs with ERR_NETWORK_CHANGED and notifies their + // requests. Might start new jobs. + void AbortAllInProgressJobs(); + + // Attempts to serve each Job in |jobs_| from the HOSTS file if we have + // a DnsClient with a valid DnsConfig. + void TryServingAllJobsFromHosts(); + + // NetworkChangeNotifier::IPAddressObserver: + virtual void OnIPAddressChanged() OVERRIDE; + + // NetworkChangeNotifier::DNSObserver: + virtual void OnDNSChanged() OVERRIDE; + + // True if have a DnsClient with a valid DnsConfig. + bool HaveDnsConfig() const; + + // Called when a host name is successfully resolved and DnsTask was run on it + // and resulted in |net_error|. + void OnDnsTaskResolve(int net_error); + + // Allows the tests to catch slots leaking out of the dispatcher. + size_t num_running_jobs_for_tests() const { + return dispatcher_.num_running_jobs(); + } + + // Cache of host resolution results. + scoped_ptr<HostCache> cache_; + + // Map from HostCache::Key to a Job. + JobMap jobs_; + + // Starts Jobs according to their priority and the configured limits. + PrioritizedDispatcher dispatcher_; + + // Limit on the maximum number of jobs queued in |dispatcher_|. + size_t max_queued_jobs_; + + // Parameters for ProcTask. + ProcTaskParams proc_params_; + + NetLog* net_log_; + + // Address family to use when the request doesn't specify one. + AddressFamily default_address_family_; + + base::WeakPtrFactory<HostResolverImpl> weak_ptr_factory_; + + base::WeakPtrFactory<HostResolverImpl> probe_weak_ptr_factory_; + + // If present, used by DnsTask and ServeFromHosts to resolve requests. + scoped_ptr<DnsClient> dns_client_; + + // True if received valid config from |dns_config_service_|. Temporary, used + // to measure performance of DnsConfigService: http://crbug.com/125599 + bool received_dns_config_; + + // Number of consecutive failures of DnsTask, counted when fallback succeeds. + unsigned num_dns_failures_; + + // Indicate if probing is done after each network change event to set address + // family. When false, explicit setting of address family is used and results + // of the IPv6 probe job are ignored. + bool ipv6_probe_monitoring_; + + // True iff ProcTask has successfully resolved a hostname known to have IPv6 + // addresses using ADDRESS_FAMILY_UNSPECIFIED. Reset on IP address change. + bool resolved_known_ipv6_hostname_; + + // Any resolver flags that should be added to a request by default. + HostResolverFlags additional_resolver_flags_; + + DISALLOW_COPY_AND_ASSIGN(HostResolverImpl); +}; + +} // namespace net + +#endif // NET_DNS_HOST_RESOLVER_IMPL_H_ diff --git a/net/dns/host_resolver_impl_unittest.cc b/net/dns/host_resolver_impl_unittest.cc new file mode 100644 index 0000000..4880b65 --- /dev/null +++ b/net/dns/host_resolver_impl_unittest.cc @@ -0,0 +1,1481 @@ +// 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/host_resolver_impl.h" + +#include <algorithm> +#include <string> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_vector.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/test/test_timeouts.h" +#include "base/time.h" +#include "net/base/address_list.h" +#include "net/base/host_cache.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/dns/dns_client.h" +#include "net/dns/dns_test_util.h" +#include "net/dns/mock_host_resolver.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +const size_t kMaxJobs = 10u; +const size_t kMaxRetryAttempts = 4u; + +PrioritizedDispatcher::Limits DefaultLimits() { + PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, kMaxJobs); + return limits; +} + +HostResolverImpl::ProcTaskParams DefaultParams( + HostResolverProc* resolver_proc) { + return HostResolverImpl::ProcTaskParams(resolver_proc, kMaxRetryAttempts); +} + +// A HostResolverProc that pushes each host mapped into a list and allows +// waiting for a specific number of requests. Unlike RuleBasedHostResolverProc +// it never calls SystemHostResolverProc. By default resolves all hostnames to +// "127.0.0.1". After AddRule(), it resolves only names explicitly specified. +class MockHostResolverProc : public HostResolverProc { + public: + struct ResolveKey { + ResolveKey(const std::string& hostname, AddressFamily address_family) + : hostname(hostname), address_family(address_family) {} + bool operator<(const ResolveKey& other) const { + return address_family < other.address_family || + (address_family == other.address_family && hostname < other.hostname); + } + std::string hostname; + AddressFamily address_family; + }; + + typedef std::vector<ResolveKey> CaptureList; + + MockHostResolverProc() + : HostResolverProc(NULL), + num_requests_waiting_(0), + num_slots_available_(0), + requests_waiting_(&lock_), + slots_available_(&lock_) { + } + + // Waits until |count| calls to |Resolve| are blocked. Returns false when + // timed out. + bool WaitFor(unsigned count) { + base::AutoLock lock(lock_); + base::Time start_time = base::Time::Now(); + while (num_requests_waiting_ < count) { + requests_waiting_.TimedWait(TestTimeouts::action_timeout()); + if (base::Time::Now() > start_time + TestTimeouts::action_timeout()) + return false; + } + return true; + } + + // Signals |count| waiting calls to |Resolve|. First come first served. + void SignalMultiple(unsigned count) { + base::AutoLock lock(lock_); + num_slots_available_ += count; + slots_available_.Broadcast(); + } + + // Signals all waiting calls to |Resolve|. Beware of races. + void SignalAll() { + base::AutoLock lock(lock_); + num_slots_available_ = num_requests_waiting_; + slots_available_.Broadcast(); + } + + void AddRule(const std::string& hostname, AddressFamily family, + const AddressList& result) { + base::AutoLock lock(lock_); + rules_[ResolveKey(hostname, family)] = result; + } + + void AddRule(const std::string& hostname, AddressFamily family, + const std::string& ip_list) { + AddressList result; + int rv = ParseAddressList(ip_list, "", &result); + DCHECK_EQ(OK, rv); + AddRule(hostname, family, result); + } + + void AddRuleForAllFamilies(const std::string& hostname, + const std::string& ip_list) { + AddressList result; + int rv = ParseAddressList(ip_list, "", &result); + DCHECK_EQ(OK, rv); + AddRule(hostname, ADDRESS_FAMILY_UNSPECIFIED, result); + AddRule(hostname, ADDRESS_FAMILY_IPV4, result); + AddRule(hostname, ADDRESS_FAMILY_IPV6, result); + } + + virtual int Resolve(const std::string& hostname, + AddressFamily address_family, + HostResolverFlags host_resolver_flags, + AddressList* addrlist, + int* os_error) OVERRIDE { + base::AutoLock lock(lock_); + capture_list_.push_back(ResolveKey(hostname, address_family)); + ++num_requests_waiting_; + requests_waiting_.Broadcast(); + while (!num_slots_available_) + slots_available_.Wait(); + DCHECK_GT(num_requests_waiting_, 0u); + --num_slots_available_; + --num_requests_waiting_; + if (rules_.empty()) { + int rv = ParseAddressList("127.0.0.1", "", addrlist); + DCHECK_EQ(OK, rv); + return OK; + } + ResolveKey key(hostname, address_family); + if (rules_.count(key) == 0) + return ERR_NAME_NOT_RESOLVED; + *addrlist = rules_[key]; + return OK; + } + + CaptureList GetCaptureList() const { + CaptureList copy; + { + base::AutoLock lock(lock_); + copy = capture_list_; + } + return copy; + } + + bool HasBlockedRequests() const { + base::AutoLock lock(lock_); + return num_requests_waiting_ > num_slots_available_; + } + + protected: + virtual ~MockHostResolverProc() {} + + private: + mutable base::Lock lock_; + std::map<ResolveKey, AddressList> rules_; + CaptureList capture_list_; + unsigned num_requests_waiting_; + unsigned num_slots_available_; + base::ConditionVariable requests_waiting_; + base::ConditionVariable slots_available_; + + DISALLOW_COPY_AND_ASSIGN(MockHostResolverProc); +}; + +// A wrapper for requests to a HostResolver. +class Request { + public: + // Base class of handlers to be executed on completion of requests. + struct Handler { + virtual ~Handler() {} + virtual void Handle(Request* request) = 0; + }; + + Request(const HostResolver::RequestInfo& info, + size_t index, + HostResolver* resolver, + Handler* handler) + : info_(info), + index_(index), + resolver_(resolver), + handler_(handler), + quit_on_complete_(false), + result_(ERR_UNEXPECTED), + handle_(NULL) {} + + int Resolve() { + DCHECK(resolver_); + DCHECK(!handle_); + list_ = AddressList(); + result_ = resolver_->Resolve( + info_, &list_, base::Bind(&Request::OnComplete, base::Unretained(this)), + &handle_, BoundNetLog()); + if (!list_.empty()) + EXPECT_EQ(OK, result_); + return result_; + } + + int ResolveFromCache() { + DCHECK(resolver_); + DCHECK(!handle_); + return resolver_->ResolveFromCache(info_, &list_, BoundNetLog()); + } + + void Cancel() { + DCHECK(resolver_); + DCHECK(handle_); + resolver_->CancelRequest(handle_); + handle_ = NULL; + } + + const HostResolver::RequestInfo& info() const { return info_; } + size_t index() const { return index_; } + const AddressList& list() const { return list_; } + int result() const { return result_; } + bool completed() const { return result_ != ERR_IO_PENDING; } + bool pending() const { return handle_ != NULL; } + + bool HasAddress(const std::string& address, int port) const { + IPAddressNumber ip; + bool rv = ParseIPLiteralToNumber(address, &ip); + DCHECK(rv); + return std::find(list_.begin(), + list_.end(), + IPEndPoint(ip, port)) != list_.end(); + } + + // Returns the number of addresses in |list_|. + unsigned NumberOfAddresses() const { + return list_.size(); + } + + bool HasOneAddress(const std::string& address, int port) const { + return HasAddress(address, port) && (NumberOfAddresses() == 1u); + } + + // Returns ERR_UNEXPECTED if timed out. + int WaitForResult() { + if (completed()) + return result_; + base::CancelableClosure closure(MessageLoop::QuitClosure()); + MessageLoop::current()->PostDelayedTask(FROM_HERE, + closure.callback(), + TestTimeouts::action_max_timeout()); + quit_on_complete_ = true; + MessageLoop::current()->Run(); + bool did_quit = !quit_on_complete_; + quit_on_complete_ = false; + closure.Cancel(); + if (did_quit) + return result_; + else + return ERR_UNEXPECTED; + } + + private: + void OnComplete(int rv) { + EXPECT_TRUE(pending()); + EXPECT_EQ(ERR_IO_PENDING, result_); + EXPECT_NE(ERR_IO_PENDING, rv); + result_ = rv; + handle_ = NULL; + if (!list_.empty()) { + EXPECT_EQ(OK, result_); + EXPECT_EQ(info_.port(), list_.front().port()); + } + if (handler_) + handler_->Handle(this); + if (quit_on_complete_) { + MessageLoop::current()->Quit(); + quit_on_complete_ = false; + } + } + + HostResolver::RequestInfo info_; + size_t index_; + HostResolver* resolver_; + Handler* handler_; + bool quit_on_complete_; + + AddressList list_; + int result_; + HostResolver::RequestHandle handle_; + + DISALLOW_COPY_AND_ASSIGN(Request); +}; + +// Using LookupAttemptHostResolverProc simulate very long lookups, and control +// which attempt resolves the host. +class LookupAttemptHostResolverProc : public HostResolverProc { + public: + LookupAttemptHostResolverProc(HostResolverProc* previous, + int attempt_number_to_resolve, + int total_attempts) + : HostResolverProc(previous), + attempt_number_to_resolve_(attempt_number_to_resolve), + current_attempt_number_(0), + total_attempts_(total_attempts), + total_attempts_resolved_(0), + resolved_attempt_number_(0), + all_done_(&lock_) { + } + + // Test harness will wait for all attempts to finish before checking the + // results. + void WaitForAllAttemptsToFinish(const base::TimeDelta& wait_time) { + base::TimeTicks end_time = base::TimeTicks::Now() + wait_time; + { + base::AutoLock auto_lock(lock_); + while (total_attempts_resolved_ != total_attempts_ && + base::TimeTicks::Now() < end_time) { + all_done_.TimedWait(end_time - base::TimeTicks::Now()); + } + } + } + + // All attempts will wait for an attempt to resolve the host. + void WaitForAnAttemptToComplete() { + base::TimeDelta wait_time = base::TimeDelta::FromSeconds(60); + base::TimeTicks end_time = base::TimeTicks::Now() + wait_time; + { + base::AutoLock auto_lock(lock_); + while (resolved_attempt_number_ == 0 && base::TimeTicks::Now() < end_time) + all_done_.TimedWait(end_time - base::TimeTicks::Now()); + } + all_done_.Broadcast(); // Tell all waiting attempts to proceed. + } + + // Returns the number of attempts that have finished the Resolve() method. + int total_attempts_resolved() { return total_attempts_resolved_; } + + // Returns the first attempt that that has resolved the host. + int resolved_attempt_number() { return resolved_attempt_number_; } + + // HostResolverProc methods. + virtual int Resolve(const std::string& host, + AddressFamily address_family, + HostResolverFlags host_resolver_flags, + AddressList* addrlist, + int* os_error) OVERRIDE { + bool wait_for_right_attempt_to_complete = true; + { + base::AutoLock auto_lock(lock_); + ++current_attempt_number_; + if (current_attempt_number_ == attempt_number_to_resolve_) { + resolved_attempt_number_ = current_attempt_number_; + wait_for_right_attempt_to_complete = false; + } + } + + if (wait_for_right_attempt_to_complete) + // Wait for the attempt_number_to_resolve_ attempt to resolve. + WaitForAnAttemptToComplete(); + + int result = ResolveUsingPrevious(host, address_family, host_resolver_flags, + addrlist, os_error); + + { + base::AutoLock auto_lock(lock_); + ++total_attempts_resolved_; + } + + all_done_.Broadcast(); // Tell all attempts to proceed. + + // Since any negative number is considered a network error, with -1 having + // special meaning (ERR_IO_PENDING). We could return the attempt that has + // resolved the host as a negative number. For example, if attempt number 3 + // resolves the host, then this method returns -4. + if (result == OK) + return -1 - resolved_attempt_number_; + else + return result; + } + + protected: + virtual ~LookupAttemptHostResolverProc() {} + + private: + int attempt_number_to_resolve_; + int current_attempt_number_; // Incremented whenever Resolve is called. + int total_attempts_; + int total_attempts_resolved_; + int resolved_attempt_number_; + + // All attempts wait for right attempt to be resolve. + base::Lock lock_; + base::ConditionVariable all_done_; +}; + +} // namespace + +class HostResolverImplTest : public testing::Test { + public: + static const int kDefaultPort = 80; + + HostResolverImplTest() : proc_(new MockHostResolverProc()) {} + + protected: + // A Request::Handler which is a proxy to the HostResolverImplTest fixture. + struct Handler : public Request::Handler { + virtual ~Handler() {} + + // Proxy functions so that classes derived from Handler can access them. + Request* CreateRequest(const HostResolver::RequestInfo& info) { + return test->CreateRequest(info); + } + Request* CreateRequest(const std::string& hostname, int port) { + return test->CreateRequest(hostname, port); + } + Request* CreateRequest(const std::string& hostname) { + return test->CreateRequest(hostname); + } + ScopedVector<Request>& requests() { return test->requests_; } + + void DeleteResolver() { test->resolver_.reset(); } + + HostResolverImplTest* test; + }; + + void CreateResolver() { + resolver_.reset(new HostResolverImpl( + HostCache::CreateDefaultCache(), + DefaultLimits(), + DefaultParams(proc_), + NULL)); + } + + // This HostResolverImpl will only allow 1 outstanding resolve at a time and + // perform no retries. + void CreateSerialResolver() { + HostResolverImpl::ProcTaskParams params = DefaultParams(proc_); + params.max_retry_attempts = 0u; + PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1); + resolver_.reset(new HostResolverImpl( + HostCache::CreateDefaultCache(), + limits, + params, + NULL)); + } + + // The Request will not be made until a call to |Resolve()|, and the Job will + // not start until released by |proc_->SignalXXX|. + Request* CreateRequest(const HostResolver::RequestInfo& info) { + Request* req = new Request(info, requests_.size(), resolver_.get(), + handler_.get()); + requests_.push_back(req); + return req; + } + + Request* CreateRequest(const std::string& hostname, + int port, + RequestPriority priority, + AddressFamily family) { + HostResolver::RequestInfo info(HostPortPair(hostname, port)); + info.set_priority(priority); + info.set_address_family(family); + return CreateRequest(info); + } + + Request* CreateRequest(const std::string& hostname, + int port, + RequestPriority priority) { + return CreateRequest(hostname, port, priority, ADDRESS_FAMILY_UNSPECIFIED); + } + + Request* CreateRequest(const std::string& hostname, int port) { + return CreateRequest(hostname, port, MEDIUM); + } + + Request* CreateRequest(const std::string& hostname) { + return CreateRequest(hostname, kDefaultPort); + } + + virtual void SetUp() OVERRIDE { + CreateResolver(); + } + + virtual void TearDown() OVERRIDE { + if (resolver_.get()) + EXPECT_EQ(0u, resolver_->num_running_jobs_for_tests()); + EXPECT_FALSE(proc_->HasBlockedRequests()); + } + + void set_handler(Handler* handler) { + handler_.reset(handler); + handler_->test = this; + } + + // Friendship is not inherited, so use proxies to access those. + size_t num_running_jobs() const { + DCHECK(resolver_.get()); + return resolver_->num_running_jobs_for_tests(); + } + + scoped_refptr<MockHostResolverProc> proc_; + scoped_ptr<HostResolverImpl> resolver_; + ScopedVector<Request> requests_; + + scoped_ptr<Handler> handler_; +}; + +TEST_F(HostResolverImplTest, AsynchronousLookup) { + proc_->AddRuleForAllFamilies("just.testing", "192.168.1.42"); + proc_->SignalMultiple(1u); + + Request* req = CreateRequest("just.testing", 80); + EXPECT_EQ(ERR_IO_PENDING, req->Resolve()); + EXPECT_EQ(OK, req->WaitForResult()); + + EXPECT_TRUE(req->HasOneAddress("192.168.1.42", 80)); + + EXPECT_EQ("just.testing", proc_->GetCaptureList()[0].hostname); +} + +TEST_F(HostResolverImplTest, FailedAsynchronousLookup) { + proc_->AddRuleForAllFamilies("", "0.0.0.0"); // Default to failures. + proc_->SignalMultiple(1u); + + Request* req = CreateRequest("just.testing", 80); + EXPECT_EQ(ERR_IO_PENDING, req->Resolve()); + EXPECT_EQ(ERR_NAME_NOT_RESOLVED, req->WaitForResult()); + + EXPECT_EQ("just.testing", proc_->GetCaptureList()[0].hostname); + + // Also test that the error is not cached. + EXPECT_EQ(ERR_DNS_CACHE_MISS, req->ResolveFromCache()); +} + +TEST_F(HostResolverImplTest, AbortedAsynchronousLookup) { + Request* req0 = CreateRequest("just.testing", 80); + EXPECT_EQ(ERR_IO_PENDING, req0->Resolve()); + + EXPECT_TRUE(proc_->WaitFor(1u)); + + // Resolver is destroyed while job is running on WorkerPool. + resolver_.reset(); + + proc_->SignalAll(); + + // To ensure there was no spurious callback, complete with a new resolver. + CreateResolver(); + Request* req1 = CreateRequest("just.testing", 80); + EXPECT_EQ(ERR_IO_PENDING, req1->Resolve()); + + proc_->SignalMultiple(2u); + + EXPECT_EQ(OK, req1->WaitForResult()); + + // This request was canceled. + EXPECT_FALSE(req0->completed()); +} + +TEST_F(HostResolverImplTest, NumericIPv4Address) { + // Stevens says dotted quads with AI_UNSPEC resolve to a single sockaddr_in. + Request* req = CreateRequest("127.1.2.3", 5555); + EXPECT_EQ(OK, req->Resolve()); + + EXPECT_TRUE(req->HasOneAddress("127.1.2.3", 5555)); +} + +TEST_F(HostResolverImplTest, NumericIPv6Address) { + // Resolve a plain IPv6 address. Don't worry about [brackets], because + // the caller should have removed them. + Request* req = CreateRequest("2001:db8::1", 5555); + EXPECT_EQ(OK, req->Resolve()); + + EXPECT_TRUE(req->HasOneAddress("2001:db8::1", 5555)); +} + +TEST_F(HostResolverImplTest, EmptyHost) { + Request* req = CreateRequest("", 5555); + EXPECT_EQ(ERR_NAME_NOT_RESOLVED, req->Resolve()); +} + +TEST_F(HostResolverImplTest, LongHost) { + Request* req = CreateRequest(std::string(4097, 'a'), 5555); + EXPECT_EQ(ERR_NAME_NOT_RESOLVED, req->Resolve()); +} + +TEST_F(HostResolverImplTest, DeDupeRequests) { + // Start 5 requests, duplicating hosts "a" and "b". Since the resolver_proc is + // blocked, these should all pile up until we signal it. + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 80)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b", 80)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b", 81)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 82)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b", 83)->Resolve()); + + proc_->SignalMultiple(2u); // One for "a", one for "b". + + for (size_t i = 0; i < requests_.size(); ++i) { + EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i; + } +} + +TEST_F(HostResolverImplTest, CancelMultipleRequests) { + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 80)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b", 80)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b", 81)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 82)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b", 83)->Resolve()); + + // Cancel everything except request for ("a", 82). + requests_[0]->Cancel(); + requests_[1]->Cancel(); + requests_[2]->Cancel(); + requests_[4]->Cancel(); + + proc_->SignalMultiple(2u); // One for "a", one for "b". + + EXPECT_EQ(OK, requests_[3]->WaitForResult()); +} + +TEST_F(HostResolverImplTest, CanceledRequestsReleaseJobSlots) { + // Fill up the dispatcher and queue. + for (unsigned i = 0; i < kMaxJobs + 1; ++i) { + std::string hostname = "a_"; + hostname[1] = 'a' + i; + EXPECT_EQ(ERR_IO_PENDING, CreateRequest(hostname, 80)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest(hostname, 81)->Resolve()); + } + + EXPECT_TRUE(proc_->WaitFor(kMaxJobs)); + + // Cancel all but last two. + for (unsigned i = 0; i < requests_.size() - 2; ++i) { + requests_[i]->Cancel(); + } + + EXPECT_TRUE(proc_->WaitFor(kMaxJobs + 1)); + + proc_->SignalAll(); + + size_t num_requests = requests_.size(); + EXPECT_EQ(OK, requests_[num_requests - 1]->WaitForResult()); + EXPECT_EQ(OK, requests_[num_requests - 2]->result()); +} + +TEST_F(HostResolverImplTest, CancelWithinCallback) { + struct MyHandler : public Handler { + virtual void Handle(Request* req) OVERRIDE { + // Port 80 is the first request that the callback will be invoked for. + // While we are executing within that callback, cancel the other requests + // in the job and start another request. + if (req->index() == 0) { + // Once "a:80" completes, it will cancel "a:81" and "a:82". + requests()[1]->Cancel(); + requests()[2]->Cancel(); + } + } + }; + set_handler(new MyHandler()); + + for (size_t i = 0; i < 4; ++i) { + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 80 + i)->Resolve()) << i; + } + + proc_->SignalMultiple(2u); // One for "a". One for "finalrequest". + + EXPECT_EQ(OK, requests_[0]->WaitForResult()); + + Request* final_request = CreateRequest("finalrequest", 70); + EXPECT_EQ(ERR_IO_PENDING, final_request->Resolve()); + EXPECT_EQ(OK, final_request->WaitForResult()); + EXPECT_TRUE(requests_[3]->completed()); +} + +TEST_F(HostResolverImplTest, DeleteWithinCallback) { + struct MyHandler : public Handler { + virtual void Handle(Request* req) OVERRIDE { + EXPECT_EQ("a", req->info().hostname()); + EXPECT_EQ(80, req->info().port()); + + DeleteResolver(); + + // Quit after returning from OnCompleted (to give it a chance at + // incorrectly running the cancelled tasks). + MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure()); + } + }; + set_handler(new MyHandler()); + + for (size_t i = 0; i < 4; ++i) { + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 80 + i)->Resolve()) << i; + } + + proc_->SignalMultiple(1u); // One for "a". + + // |MyHandler| will send quit message once all the requests have finished. + MessageLoop::current()->Run(); +} + +TEST_F(HostResolverImplTest, DeleteWithinAbortedCallback) { + struct MyHandler : public Handler { + virtual void Handle(Request* req) OVERRIDE { + EXPECT_EQ("a", req->info().hostname()); + EXPECT_EQ(80, req->info().port()); + + DeleteResolver(); + + // Quit after returning from OnCompleted (to give it a chance at + // incorrectly running the cancelled tasks). + MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure()); + } + }; + set_handler(new MyHandler()); + + // This test assumes that the Jobs will be Aborted in order ["a", "b"] + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 80)->Resolve()); + // HostResolverImpl will be deleted before later Requests can complete. + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 81)->Resolve()); + // Job for 'b' will be aborted before it can complete. + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b", 82)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b", 83)->Resolve()); + + EXPECT_TRUE(proc_->WaitFor(1u)); + + // Triggering an IP address change. + NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); + + // |MyHandler| will send quit message once all the requests have finished. + MessageLoop::current()->Run(); + + EXPECT_EQ(ERR_NETWORK_CHANGED, requests_[0]->result()); + EXPECT_EQ(ERR_IO_PENDING, requests_[1]->result()); + EXPECT_EQ(ERR_IO_PENDING, requests_[2]->result()); + EXPECT_EQ(ERR_IO_PENDING, requests_[3]->result()); + // Clean up. + proc_->SignalMultiple(requests_.size()); +} + +TEST_F(HostResolverImplTest, StartWithinCallback) { + struct MyHandler : public Handler { + virtual void Handle(Request* req) OVERRIDE { + if (req->index() == 0) { + // On completing the first request, start another request for "a". + // Since caching is disabled, this will result in another async request. + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 70)->Resolve()); + } + } + }; + set_handler(new MyHandler()); + + // Turn off caching for this host resolver. + resolver_.reset(new HostResolverImpl( + scoped_ptr<HostCache>(), + DefaultLimits(), + DefaultParams(proc_), + NULL)); + + for (size_t i = 0; i < 4; ++i) { + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 80 + i)->Resolve()) << i; + } + + proc_->SignalMultiple(2u); // One for "a". One for the second "a". + + EXPECT_EQ(OK, requests_[0]->WaitForResult()); + ASSERT_EQ(5u, requests_.size()); + EXPECT_EQ(OK, requests_.back()->WaitForResult()); + + EXPECT_EQ(2u, proc_->GetCaptureList().size()); +} + +TEST_F(HostResolverImplTest, BypassCache) { + struct MyHandler : public Handler { + virtual void Handle(Request* req) OVERRIDE { + if (req->index() == 0) { + // On completing the first request, start another request for "a". + // Since caching is enabled, this should complete synchronously. + std::string hostname = req->info().hostname(); + EXPECT_EQ(OK, CreateRequest(hostname, 70)->Resolve()); + EXPECT_EQ(OK, CreateRequest(hostname, 75)->ResolveFromCache()); + + // Ok good. Now make sure that if we ask to bypass the cache, it can no + // longer service the request synchronously. + HostResolver::RequestInfo info(HostPortPair(hostname, 71)); + info.set_allow_cached_response(false); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest(info)->Resolve()); + } else if (71 == req->info().port()) { + // Test is done. + MessageLoop::current()->Quit(); + } else { + FAIL() << "Unexpected request"; + } + } + }; + set_handler(new MyHandler()); + + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 80)->Resolve()); + proc_->SignalMultiple(3u); // Only need two, but be generous. + + // |verifier| will send quit message once all the requests have finished. + MessageLoop::current()->Run(); + EXPECT_EQ(2u, proc_->GetCaptureList().size()); +} + +// Test that IP address changes flush the cache. +TEST_F(HostResolverImplTest, FlushCacheOnIPAddressChange) { + proc_->SignalMultiple(2u); // One before the flush, one after. + + Request* req = CreateRequest("host1", 70); + EXPECT_EQ(ERR_IO_PENDING, req->Resolve()); + EXPECT_EQ(OK, req->WaitForResult()); + + req = CreateRequest("host1", 75); + EXPECT_EQ(OK, req->Resolve()); // Should complete synchronously. + + // Flush cache by triggering an IP address change. + NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); + MessageLoop::current()->RunUntilIdle(); // Notification happens async. + + // Resolve "host1" again -- this time it won't be served from cache, so it + // will complete asynchronously. + req = CreateRequest("host1", 80); + EXPECT_EQ(ERR_IO_PENDING, req->Resolve()); + EXPECT_EQ(OK, req->WaitForResult()); +} + +// Test that IP address changes send ERR_NETWORK_CHANGED to pending requests. +TEST_F(HostResolverImplTest, AbortOnIPAddressChanged) { + Request* req = CreateRequest("host1", 70); + EXPECT_EQ(ERR_IO_PENDING, req->Resolve()); + + EXPECT_TRUE(proc_->WaitFor(1u)); + // Triggering an IP address change. + NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); + MessageLoop::current()->RunUntilIdle(); // Notification happens async. + proc_->SignalAll(); + + EXPECT_EQ(ERR_NETWORK_CHANGED, req->WaitForResult()); + EXPECT_EQ(0u, resolver_->GetHostCache()->size()); +} + +// Obey pool constraints after IP address has changed. +TEST_F(HostResolverImplTest, ObeyPoolConstraintsAfterIPAddressChange) { + // Runs at most one job at a time. + CreateSerialResolver(); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a")->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b")->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("c")->Resolve()); + + EXPECT_TRUE(proc_->WaitFor(1u)); + // Triggering an IP address change. + NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); + MessageLoop::current()->RunUntilIdle(); // Notification happens async. + proc_->SignalMultiple(3u); // Let the false-start go so that we can catch it. + + EXPECT_EQ(ERR_NETWORK_CHANGED, requests_[0]->WaitForResult()); + + EXPECT_EQ(1u, num_running_jobs()); + + EXPECT_FALSE(requests_[1]->completed()); + EXPECT_FALSE(requests_[2]->completed()); + + EXPECT_EQ(OK, requests_[2]->WaitForResult()); + EXPECT_EQ(OK, requests_[1]->result()); +} + +// Tests that a new Request made from the callback of a previously aborted one +// will not be aborted. +TEST_F(HostResolverImplTest, AbortOnlyExistingRequestsOnIPAddressChange) { + struct MyHandler : public Handler { + virtual void Handle(Request* req) OVERRIDE { + // Start new request for a different hostname to ensure that the order + // of jobs in HostResolverImpl is not stable. + std::string hostname; + if (req->index() == 0) + hostname = "zzz"; + else if (req->index() == 1) + hostname = "aaa"; + else if (req->index() == 2) + hostname = "eee"; + else + return; // A request started from within MyHandler. + EXPECT_EQ(ERR_IO_PENDING, CreateRequest(hostname)->Resolve()) << hostname; + } + }; + set_handler(new MyHandler()); + + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("bbb")->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("eee")->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ccc")->Resolve()); + + // Wait until all are blocked; + EXPECT_TRUE(proc_->WaitFor(3u)); + // Trigger an IP address change. + NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); + // This should abort all running jobs. + MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(ERR_NETWORK_CHANGED, requests_[0]->result()); + EXPECT_EQ(ERR_NETWORK_CHANGED, requests_[1]->result()); + EXPECT_EQ(ERR_NETWORK_CHANGED, requests_[2]->result()); + ASSERT_EQ(6u, requests_.size()); + // Unblock all calls to proc. + proc_->SignalMultiple(requests_.size()); + // Run until the re-started requests finish. + EXPECT_EQ(OK, requests_[3]->WaitForResult()); + EXPECT_EQ(OK, requests_[4]->WaitForResult()); + EXPECT_EQ(OK, requests_[5]->WaitForResult()); + // Verify that results of aborted Jobs were not cached. + EXPECT_EQ(6u, proc_->GetCaptureList().size()); + EXPECT_EQ(3u, resolver_->GetHostCache()->size()); +} + +// Tests that when the maximum threads is set to 1, requests are dequeued +// in order of priority. +TEST_F(HostResolverImplTest, HigherPriorityRequestsStartedFirst) { + CreateSerialResolver(); + + // Note that at this point the MockHostResolverProc is blocked, so any + // requests we make will not complete. + CreateRequest("req0", 80, LOW); + CreateRequest("req1", 80, MEDIUM); + CreateRequest("req2", 80, MEDIUM); + CreateRequest("req3", 80, LOW); + CreateRequest("req4", 80, HIGHEST); + CreateRequest("req5", 80, LOW); + CreateRequest("req6", 80, LOW); + CreateRequest("req5", 80, HIGHEST); + + for (size_t i = 0; i < requests_.size(); ++i) { + EXPECT_EQ(ERR_IO_PENDING, requests_[i]->Resolve()) << i; + } + + // Unblock the resolver thread so the requests can run. + proc_->SignalMultiple(requests_.size()); // More than needed. + + // Wait for all the requests to complete succesfully. + for (size_t i = 0; i < requests_.size(); ++i) { + EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i; + } + + // Since we have restricted to a single concurrent thread in the jobpool, + // the requests should complete in order of priority (with the exception + // of the first request, which gets started right away, since there is + // nothing outstanding). + MockHostResolverProc::CaptureList capture_list = proc_->GetCaptureList(); + ASSERT_EQ(7u, capture_list.size()); + + EXPECT_EQ("req0", capture_list[0].hostname); + EXPECT_EQ("req4", capture_list[1].hostname); + EXPECT_EQ("req5", capture_list[2].hostname); + EXPECT_EQ("req1", capture_list[3].hostname); + EXPECT_EQ("req2", capture_list[4].hostname); + EXPECT_EQ("req3", capture_list[5].hostname); + EXPECT_EQ("req6", capture_list[6].hostname); +} + +// Try cancelling a job which has not started yet. +TEST_F(HostResolverImplTest, CancelPendingRequest) { + CreateSerialResolver(); + + CreateRequest("req0", 80, LOWEST); + CreateRequest("req1", 80, HIGHEST); // Will cancel. + CreateRequest("req2", 80, MEDIUM); + CreateRequest("req3", 80, LOW); + CreateRequest("req4", 80, HIGHEST); // Will cancel. + CreateRequest("req5", 80, LOWEST); // Will cancel. + CreateRequest("req6", 80, MEDIUM); + + // Start all of the requests. + for (size_t i = 0; i < requests_.size(); ++i) { + EXPECT_EQ(ERR_IO_PENDING, requests_[i]->Resolve()) << i; + } + + // Cancel some requests + requests_[1]->Cancel(); + requests_[4]->Cancel(); + requests_[5]->Cancel(); + + // Unblock the resolver thread so the requests can run. + proc_->SignalMultiple(requests_.size()); // More than needed. + + // Wait for all the requests to complete succesfully. + for (size_t i = 0; i < requests_.size(); ++i) { + if (!requests_[i]->pending()) + continue; // Don't wait for the requests we cancelled. + EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i; + } + + // Verify that they called out the the resolver proc (which runs on the + // resolver thread) in the expected order. + MockHostResolverProc::CaptureList capture_list = proc_->GetCaptureList(); + ASSERT_EQ(4u, capture_list.size()); + + EXPECT_EQ("req0", capture_list[0].hostname); + EXPECT_EQ("req2", capture_list[1].hostname); + EXPECT_EQ("req6", capture_list[2].hostname); + EXPECT_EQ("req3", capture_list[3].hostname); +} + +// Test that when too many requests are enqueued, old ones start to be aborted. +TEST_F(HostResolverImplTest, QueueOverflow) { + CreateSerialResolver(); + + // Allow only 3 queued jobs. + const size_t kMaxPendingJobs = 3u; + resolver_->SetMaxQueuedJobs(kMaxPendingJobs); + + // Note that at this point the MockHostResolverProc is blocked, so any + // requests we make will not complete. + + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("req0", 80, LOWEST)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("req1", 80, HIGHEST)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("req2", 80, MEDIUM)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("req3", 80, MEDIUM)->Resolve()); + + // At this point, there are 3 enqueued jobs. + // Insertion of subsequent requests will cause evictions + // based on priority. + + EXPECT_EQ(ERR_HOST_RESOLVER_QUEUE_TOO_LARGE, + CreateRequest("req4", 80, LOW)->Resolve()); // Evicts itself! + + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("req5", 80, MEDIUM)->Resolve()); + EXPECT_EQ(ERR_HOST_RESOLVER_QUEUE_TOO_LARGE, requests_[2]->result()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("req6", 80, HIGHEST)->Resolve()); + EXPECT_EQ(ERR_HOST_RESOLVER_QUEUE_TOO_LARGE, requests_[3]->result()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("req7", 80, MEDIUM)->Resolve()); + EXPECT_EQ(ERR_HOST_RESOLVER_QUEUE_TOO_LARGE, requests_[5]->result()); + + // Unblock the resolver thread so the requests can run. + proc_->SignalMultiple(4u); + + // The rest should succeed. + EXPECT_EQ(OK, requests_[7]->WaitForResult()); + EXPECT_EQ(OK, requests_[0]->result()); + EXPECT_EQ(OK, requests_[1]->result()); + EXPECT_EQ(OK, requests_[6]->result()); + + // Verify that they called out the the resolver proc (which runs on the + // resolver thread) in the expected order. + MockHostResolverProc::CaptureList capture_list = proc_->GetCaptureList(); + ASSERT_EQ(4u, capture_list.size()); + + EXPECT_EQ("req0", capture_list[0].hostname); + EXPECT_EQ("req1", capture_list[1].hostname); + EXPECT_EQ("req6", capture_list[2].hostname); + EXPECT_EQ("req7", capture_list[3].hostname); + + // Verify that the evicted (incomplete) requests were not cached. + EXPECT_EQ(4u, resolver_->GetHostCache()->size()); + + for (size_t i = 0; i < requests_.size(); ++i) { + EXPECT_TRUE(requests_[i]->completed()) << i; + } +} + +// Tests that after changing the default AddressFamily to IPV4, requests +// with UNSPECIFIED address family map to IPV4. +TEST_F(HostResolverImplTest, SetDefaultAddressFamily_IPv4) { + CreateSerialResolver(); // To guarantee order of resolutions. + + proc_->AddRule("h1", ADDRESS_FAMILY_IPV4, "1.0.0.1"); + proc_->AddRule("h1", ADDRESS_FAMILY_IPV6, "::2"); + + resolver_->SetDefaultAddressFamily(ADDRESS_FAMILY_IPV4); + + CreateRequest("h1", 80, MEDIUM, ADDRESS_FAMILY_UNSPECIFIED); + CreateRequest("h1", 80, MEDIUM, ADDRESS_FAMILY_IPV4); + CreateRequest("h1", 80, MEDIUM, ADDRESS_FAMILY_IPV6); + + // Start all of the requests. + for (size_t i = 0; i < requests_.size(); ++i) { + EXPECT_EQ(ERR_IO_PENDING, requests_[i]->Resolve()) << i; + } + + proc_->SignalMultiple(requests_.size()); + + // Wait for all the requests to complete. + for (size_t i = 0u; i < requests_.size(); ++i) { + EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i; + } + + // Since the requests all had the same priority and we limited the thread + // count to 1, they should have completed in the same order as they were + // requested. Moreover, request0 and request1 will have been serviced by + // the same job. + + MockHostResolverProc::CaptureList capture_list = proc_->GetCaptureList(); + ASSERT_EQ(2u, capture_list.size()); + + EXPECT_EQ("h1", capture_list[0].hostname); + EXPECT_EQ(ADDRESS_FAMILY_IPV4, capture_list[0].address_family); + + EXPECT_EQ("h1", capture_list[1].hostname); + EXPECT_EQ(ADDRESS_FAMILY_IPV6, capture_list[1].address_family); + + // Now check that the correct resolved IP addresses were returned. + EXPECT_TRUE(requests_[0]->HasOneAddress("1.0.0.1", 80)); + EXPECT_TRUE(requests_[1]->HasOneAddress("1.0.0.1", 80)); + EXPECT_TRUE(requests_[2]->HasOneAddress("::2", 80)); +} + +// This is the exact same test as SetDefaultAddressFamily_IPv4, except the +// default family is set to IPv6 and the family of requests is flipped where +// specified. +TEST_F(HostResolverImplTest, SetDefaultAddressFamily_IPv6) { + CreateSerialResolver(); // To guarantee order of resolutions. + + // Don't use IPv6 replacements here since some systems don't support it. + proc_->AddRule("h1", ADDRESS_FAMILY_IPV4, "1.0.0.1"); + proc_->AddRule("h1", ADDRESS_FAMILY_IPV6, "::2"); + + resolver_->SetDefaultAddressFamily(ADDRESS_FAMILY_IPV6); + + CreateRequest("h1", 80, MEDIUM, ADDRESS_FAMILY_UNSPECIFIED); + CreateRequest("h1", 80, MEDIUM, ADDRESS_FAMILY_IPV6); + CreateRequest("h1", 80, MEDIUM, ADDRESS_FAMILY_IPV4); + + // Start all of the requests. + for (size_t i = 0; i < requests_.size(); ++i) { + EXPECT_EQ(ERR_IO_PENDING, requests_[i]->Resolve()) << i; + } + + proc_->SignalMultiple(requests_.size()); + + // Wait for all the requests to complete. + for (size_t i = 0u; i < requests_.size(); ++i) { + EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i; + } + + // Since the requests all had the same priority and we limited the thread + // count to 1, they should have completed in the same order as they were + // requested. Moreover, request0 and request1 will have been serviced by + // the same job. + + MockHostResolverProc::CaptureList capture_list = proc_->GetCaptureList(); + ASSERT_EQ(2u, capture_list.size()); + + EXPECT_EQ("h1", capture_list[0].hostname); + EXPECT_EQ(ADDRESS_FAMILY_IPV6, capture_list[0].address_family); + + EXPECT_EQ("h1", capture_list[1].hostname); + EXPECT_EQ(ADDRESS_FAMILY_IPV4, capture_list[1].address_family); + + // Now check that the correct resolved IP addresses were returned. + EXPECT_TRUE(requests_[0]->HasOneAddress("::2", 80)); + EXPECT_TRUE(requests_[1]->HasOneAddress("::2", 80)); + EXPECT_TRUE(requests_[2]->HasOneAddress("1.0.0.1", 80)); +} + +TEST_F(HostResolverImplTest, ResolveFromCache) { + proc_->AddRuleForAllFamilies("just.testing", "192.168.1.42"); + proc_->SignalMultiple(1u); // Need only one. + + HostResolver::RequestInfo info(HostPortPair("just.testing", 80)); + + // First hit will miss the cache. + EXPECT_EQ(ERR_DNS_CACHE_MISS, CreateRequest(info)->ResolveFromCache()); + + // This time, we fetch normally. + EXPECT_EQ(ERR_IO_PENDING, CreateRequest(info)->Resolve()); + EXPECT_EQ(OK, requests_[1]->WaitForResult()); + + // Now we should be able to fetch from the cache. + EXPECT_EQ(OK, CreateRequest(info)->ResolveFromCache()); + EXPECT_TRUE(requests_[2]->HasOneAddress("192.168.1.42", 80)); +} + +// Test the retry attempts simulating host resolver proc that takes too long. +TEST_F(HostResolverImplTest, MultipleAttempts) { + // Total number of attempts would be 3 and we want the 3rd attempt to resolve + // the host. First and second attempt will be forced to sleep until they get + // word that a resolution has completed. The 3rd resolution attempt will try + // to get done ASAP, and won't sleep.. + int kAttemptNumberToResolve = 3; + int kTotalAttempts = 3; + + scoped_refptr<LookupAttemptHostResolverProc> resolver_proc( + new LookupAttemptHostResolverProc( + NULL, kAttemptNumberToResolve, kTotalAttempts)); + + HostResolverImpl::ProcTaskParams params = DefaultParams(resolver_proc.get()); + + // Specify smaller interval for unresponsive_delay_ for HostResolverImpl so + // that unit test runs faster. For example, this test finishes in 1.5 secs + // (500ms * 3). + params.unresponsive_delay = base::TimeDelta::FromMilliseconds(500); + + resolver_.reset( + new HostResolverImpl(HostCache::CreateDefaultCache(), + DefaultLimits(), + params, + NULL)); + + // Resolve "host1". + HostResolver::RequestInfo info(HostPortPair("host1", 70)); + Request* req = CreateRequest(info); + EXPECT_EQ(ERR_IO_PENDING, req->Resolve()); + + // Resolve returns -4 to indicate that 3rd attempt has resolved the host. + EXPECT_EQ(-4, req->WaitForResult()); + + resolver_proc->WaitForAllAttemptsToFinish( + base::TimeDelta::FromMilliseconds(60000)); + MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(resolver_proc->total_attempts_resolved(), kTotalAttempts); + EXPECT_EQ(resolver_proc->resolved_attempt_number(), kAttemptNumberToResolve); +} + +DnsConfig CreateValidDnsConfig() { + IPAddressNumber dns_ip; + bool rv = ParseIPLiteralToNumber("192.168.1.0", &dns_ip); + EXPECT_TRUE(rv); + + DnsConfig config; + config.nameservers.push_back(IPEndPoint(dns_ip, dns_protocol::kDefaultPort)); + EXPECT_TRUE(config.IsValid()); + return config; +} + +// Specialized fixture for tests of DnsTask. +class HostResolverImplDnsTest : public HostResolverImplTest { + protected: + virtual void SetUp() OVERRIDE { + AddDnsRule("er", dns_protocol::kTypeA, MockDnsClientRule::FAIL_SYNC); + AddDnsRule("er", dns_protocol::kTypeAAAA, MockDnsClientRule::FAIL_SYNC); + AddDnsRule("nx", dns_protocol::kTypeA, MockDnsClientRule::FAIL_ASYNC); + AddDnsRule("nx", dns_protocol::kTypeAAAA, MockDnsClientRule::FAIL_ASYNC); + AddDnsRule("ok", dns_protocol::kTypeA, MockDnsClientRule::OK); + AddDnsRule("ok", dns_protocol::kTypeAAAA, MockDnsClientRule::OK); + AddDnsRule("4ok", dns_protocol::kTypeA, MockDnsClientRule::OK); + AddDnsRule("4ok", dns_protocol::kTypeAAAA, MockDnsClientRule::EMPTY); + AddDnsRule("6ok", dns_protocol::kTypeA, MockDnsClientRule::EMPTY); + AddDnsRule("6ok", dns_protocol::kTypeAAAA, MockDnsClientRule::OK); + AddDnsRule("4nx", dns_protocol::kTypeA, MockDnsClientRule::OK); + AddDnsRule("4nx", dns_protocol::kTypeAAAA, MockDnsClientRule::FAIL_ASYNC); + CreateResolver(); + } + + void CreateResolver() { + resolver_.reset(new HostResolverImpl( + HostCache::CreateDefaultCache(), + DefaultLimits(), + DefaultParams(proc_), + NULL)); + resolver_->SetDnsClient(CreateMockDnsClient(DnsConfig(), dns_rules_)); + } + + // Adds a rule to |dns_rules_|. Must be followed by |CreateResolver| to apply. + void AddDnsRule(const std::string& prefix, + uint16 qtype, + MockDnsClientRule::Result result) { + dns_rules_.push_back(MockDnsClientRule(prefix, qtype, result)); + } + + void ChangeDnsConfig(const DnsConfig& config) { + NetworkChangeNotifier::SetDnsConfig(config); + // Notification is delivered asynchronously. + MessageLoop::current()->RunUntilIdle(); + } + + MockDnsClientRuleList dns_rules_; +}; + +// TODO(szym): Test AbortAllInProgressJobs due to DnsConfig change. + +// TODO(cbentzel): Test a mix of requests with different HostResolverFlags. + +// Test successful and fallback resolutions in HostResolverImpl::DnsTask. +TEST_F(HostResolverImplDnsTest, DnsTask) { + resolver_->SetDefaultAddressFamily(ADDRESS_FAMILY_IPV4); + + proc_->AddRuleForAllFamilies("er_succeed", "192.168.1.101"); + proc_->AddRuleForAllFamilies("nx_succeed", "192.168.1.102"); + // All other hostnames will fail in proc_. + + // Initially there is no config, so client should not be invoked. + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ok_fail", 80)->Resolve()); + proc_->SignalMultiple(requests_.size()); + + EXPECT_EQ(ERR_NAME_NOT_RESOLVED, requests_[0]->WaitForResult()); + + ChangeDnsConfig(CreateValidDnsConfig()); + + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ok_fail", 80)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("er_fail", 80)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("nx_fail", 80)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("er_succeed", 80)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("nx_succeed", 80)->Resolve()); + + proc_->SignalMultiple(requests_.size()); + + for (size_t i = 1; i < requests_.size(); ++i) + EXPECT_NE(ERR_UNEXPECTED, requests_[i]->WaitForResult()) << i; + + EXPECT_EQ(OK, requests_[1]->result()); + // Resolved by MockDnsClient. + EXPECT_TRUE(requests_[1]->HasOneAddress("127.0.0.1", 80)); + // Fallback to ProcTask. + EXPECT_EQ(ERR_NAME_NOT_RESOLVED, requests_[2]->result()); + EXPECT_EQ(ERR_NAME_NOT_RESOLVED, requests_[3]->result()); + EXPECT_EQ(OK, requests_[4]->result()); + EXPECT_TRUE(requests_[4]->HasOneAddress("192.168.1.101", 80)); + EXPECT_EQ(OK, requests_[5]->result()); + EXPECT_TRUE(requests_[5]->HasOneAddress("192.168.1.102", 80)); +} + +TEST_F(HostResolverImplDnsTest, DnsTaskUnspec) { + ChangeDnsConfig(CreateValidDnsConfig()); + + proc_->AddRuleForAllFamilies("4nx", "192.168.1.101"); + // All other hostnames will fail in proc_. + + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ok", 80)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("4ok", 80)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("6ok", 80)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("4nx", 80)->Resolve()); + + proc_->SignalMultiple(requests_.size()); + + for (size_t i = 0; i < requests_.size(); ++i) + EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i; + + EXPECT_EQ(2u, requests_[0]->NumberOfAddresses()); + EXPECT_TRUE(requests_[0]->HasAddress("127.0.0.1", 80)); + EXPECT_TRUE(requests_[0]->HasAddress("::1", 80)); + EXPECT_EQ(1u, requests_[1]->NumberOfAddresses()); + EXPECT_TRUE(requests_[1]->HasAddress("127.0.0.1", 80)); + EXPECT_EQ(1u, requests_[2]->NumberOfAddresses()); + EXPECT_TRUE(requests_[2]->HasAddress("::1", 80)); + EXPECT_EQ(1u, requests_[3]->NumberOfAddresses()); + EXPECT_TRUE(requests_[3]->HasAddress("192.168.1.101", 80)); +} + +TEST_F(HostResolverImplDnsTest, ServeFromHosts) { + // Initially, use empty HOSTS file. + DnsConfig config = CreateValidDnsConfig(); + ChangeDnsConfig(config); + + proc_->AddRuleForAllFamilies("", ""); // Default to failures. + proc_->SignalMultiple(1u); // For the first request which misses. + + Request* req0 = CreateRequest("er_ipv4", 80); + EXPECT_EQ(ERR_IO_PENDING, req0->Resolve()); + EXPECT_EQ(ERR_NAME_NOT_RESOLVED, req0->WaitForResult()); + + IPAddressNumber local_ipv4, local_ipv6; + ASSERT_TRUE(ParseIPLiteralToNumber("127.0.0.1", &local_ipv4)); + ASSERT_TRUE(ParseIPLiteralToNumber("::1", &local_ipv6)); + + DnsHosts hosts; + hosts[DnsHostsKey("er_ipv4", ADDRESS_FAMILY_IPV4)] = local_ipv4; + hosts[DnsHostsKey("er_ipv6", ADDRESS_FAMILY_IPV6)] = local_ipv6; + hosts[DnsHostsKey("er_both", ADDRESS_FAMILY_IPV4)] = local_ipv4; + hosts[DnsHostsKey("er_both", ADDRESS_FAMILY_IPV6)] = local_ipv6; + + // Update HOSTS file. + config.hosts = hosts; + ChangeDnsConfig(config); + + Request* req1 = CreateRequest("er_ipv4", 80); + EXPECT_EQ(OK, req1->Resolve()); + EXPECT_TRUE(req1->HasOneAddress("127.0.0.1", 80)); + + Request* req2 = CreateRequest("er_ipv6", 80); + EXPECT_EQ(OK, req2->Resolve()); + EXPECT_TRUE(req2->HasOneAddress("::1", 80)); + + Request* req3 = CreateRequest("er_both", 80); + EXPECT_EQ(OK, req3->Resolve()); + EXPECT_TRUE(req3->HasOneAddress("127.0.0.1", 80) || + req3->HasOneAddress("::1", 80)); + + // Requests with specified AddressFamily. + Request* req4 = CreateRequest("er_ipv4", 80, MEDIUM, ADDRESS_FAMILY_IPV4); + EXPECT_EQ(OK, req4->Resolve()); + EXPECT_TRUE(req4->HasOneAddress("127.0.0.1", 80)); + + Request* req5 = CreateRequest("er_ipv6", 80, MEDIUM, ADDRESS_FAMILY_IPV6); + EXPECT_EQ(OK, req5->Resolve()); + EXPECT_TRUE(req5->HasOneAddress("::1", 80)); + + // Request with upper case. + Request* req6 = CreateRequest("er_IPV4", 80); + EXPECT_EQ(OK, req6->Resolve()); + EXPECT_TRUE(req6->HasOneAddress("127.0.0.1", 80)); +} + +TEST_F(HostResolverImplDnsTest, BypassDnsTask) { + ChangeDnsConfig(CreateValidDnsConfig()); + + proc_->AddRuleForAllFamilies("", ""); // Default to failures. + + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ok.local", 80)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ok.local.", 80)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("oklocal", 80)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("oklocal.", 80)->Resolve()); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ok", 80)->Resolve()); + + proc_->SignalMultiple(requests_.size()); + + for (size_t i = 0; i < 2; ++i) + EXPECT_EQ(ERR_NAME_NOT_RESOLVED, requests_[i]->WaitForResult()) << i; + + for (size_t i = 2; i < requests_.size(); ++i) + EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i; +} + +TEST_F(HostResolverImplDnsTest, DisableDnsClientOnPersistentFailure) { + ChangeDnsConfig(CreateValidDnsConfig()); + + proc_->AddRuleForAllFamilies("", ""); // Default to failures. + + // Check that DnsTask works. + Request* req = CreateRequest("ok_1", 80); + EXPECT_EQ(ERR_IO_PENDING, req->Resolve()); + EXPECT_EQ(OK, req->WaitForResult()); + + for (unsigned i = 0; i < 20; ++i) { + // Use custom names to require separate Jobs. + std::string hostname = base::StringPrintf("err_%u", i); + // Ensure fallback to ProcTask succeeds. + proc_->AddRuleForAllFamilies(hostname, "192.168.1.101"); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest(hostname, 80)->Resolve()) << i; + } + + proc_->SignalMultiple(requests_.size()); + + for (size_t i = 0; i < requests_.size(); ++i) + EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i; + + ASSERT_FALSE(proc_->HasBlockedRequests()); + + // DnsTask should be disabled by now. + req = CreateRequest("ok_2", 80); + EXPECT_EQ(ERR_IO_PENDING, req->Resolve()); + proc_->SignalMultiple(1u); + EXPECT_EQ(ERR_NAME_NOT_RESOLVED, req->WaitForResult()); + + // Check that it is re-enabled after DNS change. + ChangeDnsConfig(CreateValidDnsConfig()); + req = CreateRequest("ok_3", 80); + EXPECT_EQ(ERR_IO_PENDING, req->Resolve()); + EXPECT_EQ(OK, req->WaitForResult()); +} + +TEST_F(HostResolverImplDnsTest, DontDisableDnsClientOnSporadicFailure) { + ChangeDnsConfig(CreateValidDnsConfig()); + + // |proc_| defaults to successes. + + // 20 failures interleaved with 20 successes. + for (unsigned i = 0; i < 40; ++i) { + // Use custom names to require separate Jobs. + std::string hostname = (i % 2) == 0 ? base::StringPrintf("err_%u", i) + : base::StringPrintf("ok_%u", i); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest(hostname, 80)->Resolve()) << i; + } + + proc_->SignalMultiple(requests_.size()); + + for (size_t i = 0; i < requests_.size(); ++i) + EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i; + + // Make |proc_| default to failures. + proc_->AddRuleForAllFamilies("", ""); + + // DnsTask should still be enabled. + Request* req = CreateRequest("ok_last", 80); + EXPECT_EQ(ERR_IO_PENDING, req->Resolve()); + EXPECT_EQ(OK, req->WaitForResult()); +} + +} // namespace net diff --git a/net/dns/host_resolver_proc.cc b/net/dns/host_resolver_proc.cc new file mode 100644 index 0000000..6c786a1 --- /dev/null +++ b/net/dns/host_resolver_proc.cc @@ -0,0 +1,251 @@ +// 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/host_resolver_proc.h" + +#include "build/build_config.h" + +#include "base/logging.h" +#include "base/sys_byteorder.h" +#include "net/base/address_list.h" +#include "net/base/dns_reloader.h" +#include "net/base/net_errors.h" +#include "net/base/sys_addrinfo.h" + +#if defined(OS_OPENBSD) +#define AI_ADDRCONFIG 0 +#endif + +namespace net { + +namespace { + +bool IsAllLocalhostOfOneFamily(const struct addrinfo* ai) { + bool saw_v4_localhost = false; + bool saw_v6_localhost = false; + for (; ai != NULL; ai = ai->ai_next) { + switch (ai->ai_family) { + case AF_INET: { + const struct sockaddr_in* addr_in = + reinterpret_cast<struct sockaddr_in*>(ai->ai_addr); + if ((base::NetToHost32(addr_in->sin_addr.s_addr) & 0xff000000) == + 0x7f000000) + saw_v4_localhost = true; + else + return false; + break; + } + case AF_INET6: { + const struct sockaddr_in6* addr_in6 = + reinterpret_cast<struct sockaddr_in6*>(ai->ai_addr); + if (IN6_IS_ADDR_LOOPBACK(&addr_in6->sin6_addr)) + saw_v6_localhost = true; + else + return false; + break; + } + default: + NOTREACHED(); + return false; + } + } + + return saw_v4_localhost != saw_v6_localhost; +} + +} // namespace + +HostResolverProc* HostResolverProc::default_proc_ = NULL; + +HostResolverProc::HostResolverProc(HostResolverProc* previous) { + SetPreviousProc(previous); + + // Implicitly fall-back to the global default procedure. + if (!previous) + SetPreviousProc(default_proc_); +} + +HostResolverProc::~HostResolverProc() { +} + +int HostResolverProc::ResolveUsingPrevious( + const std::string& host, + AddressFamily address_family, + HostResolverFlags host_resolver_flags, + AddressList* addrlist, + int* os_error) { + if (previous_proc_) { + return previous_proc_->Resolve(host, address_family, host_resolver_flags, + addrlist, os_error); + } + + // Final fallback is the system resolver. + return SystemHostResolverProc(host, address_family, host_resolver_flags, + addrlist, os_error); +} + +void HostResolverProc::SetPreviousProc(HostResolverProc* proc) { + HostResolverProc* current_previous = previous_proc_; + previous_proc_ = NULL; + // Now that we've guaranteed |this| is the last proc in a chain, we can + // detect potential cycles using GetLastProc(). + previous_proc_ = (GetLastProc(proc) == this) ? current_previous : proc; +} + +void HostResolverProc::SetLastProc(HostResolverProc* proc) { + GetLastProc(this)->SetPreviousProc(proc); +} + +// static +HostResolverProc* HostResolverProc::GetLastProc(HostResolverProc* proc) { + if (proc == NULL) + return NULL; + HostResolverProc* last_proc = proc; + while (last_proc->previous_proc_ != NULL) + last_proc = last_proc->previous_proc_; + return last_proc; +} + +// static +HostResolverProc* HostResolverProc::SetDefault(HostResolverProc* proc) { + HostResolverProc* old = default_proc_; + default_proc_ = proc; + return old; +} + +// static +HostResolverProc* HostResolverProc::GetDefault() { + return default_proc_; +} + +int SystemHostResolverProc(const std::string& host, + AddressFamily address_family, + HostResolverFlags host_resolver_flags, + AddressList* addrlist, + int* os_error) { + if (os_error) + *os_error = 0; + + struct addrinfo* ai = NULL; + struct addrinfo hints = {0}; + + switch (address_family) { + case ADDRESS_FAMILY_IPV4: + hints.ai_family = AF_INET; + break; + case ADDRESS_FAMILY_IPV6: + hints.ai_family = AF_INET6; + break; + case ADDRESS_FAMILY_UNSPECIFIED: + hints.ai_family = AF_UNSPEC; + break; + default: + NOTREACHED(); + hints.ai_family = AF_UNSPEC; + } + +#if defined(OS_WIN) + // DO NOT USE AI_ADDRCONFIG ON WINDOWS. + // + // The following comment in <winsock2.h> is the best documentation I found + // on AI_ADDRCONFIG for Windows: + // Flags used in "hints" argument to getaddrinfo() + // - AI_ADDRCONFIG is supported starting with Vista + // - default is AI_ADDRCONFIG ON whether the flag is set or not + // because the performance penalty in not having ADDRCONFIG in + // the multi-protocol stack environment is severe; + // this defaulting may be disabled by specifying the AI_ALL flag, + // in that case AI_ADDRCONFIG must be EXPLICITLY specified to + // enable ADDRCONFIG behavior + // + // Not only is AI_ADDRCONFIG unnecessary, but it can be harmful. If the + // computer is not connected to a network, AI_ADDRCONFIG causes getaddrinfo + // to fail with WSANO_DATA (11004) for "localhost", probably because of the + // following note on AI_ADDRCONFIG in the MSDN getaddrinfo page: + // The IPv4 or IPv6 loopback address is not considered a valid global + // address. + // See http://crbug.com/5234. + // + // OpenBSD does not support it, either. + hints.ai_flags = 0; +#else + hints.ai_flags = AI_ADDRCONFIG; +#endif + + // On Linux AI_ADDRCONFIG doesn't consider loopback addreses, even if only + // loopback addresses are configured. So don't use it when there are only + // loopback addresses. + if (host_resolver_flags & HOST_RESOLVER_LOOPBACK_ONLY) + hints.ai_flags &= ~AI_ADDRCONFIG; + + if (host_resolver_flags & HOST_RESOLVER_CANONNAME) + hints.ai_flags |= AI_CANONNAME; + + // Restrict result set to only this socket type to avoid duplicates. + hints.ai_socktype = SOCK_STREAM; + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD) && \ + !defined(OS_ANDROID) + DnsReloaderMaybeReload(); +#endif + int err = getaddrinfo(host.c_str(), NULL, &hints, &ai); + bool should_retry = false; + // If the lookup was restricted (either by address family, or address + // detection), and the results where all localhost of a single family, + // maybe we should retry. There were several bugs related to these + // issues, for example http://crbug.com/42058 and http://crbug.com/49024 + if ((hints.ai_family != AF_UNSPEC || hints.ai_flags & AI_ADDRCONFIG) && + err == 0 && IsAllLocalhostOfOneFamily(ai)) { + if (host_resolver_flags & HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6) { + hints.ai_family = AF_UNSPEC; + should_retry = true; + } + if (hints.ai_flags & AI_ADDRCONFIG) { + hints.ai_flags &= ~AI_ADDRCONFIG; + should_retry = true; + } + } + if (should_retry) { + if (ai != NULL) { + freeaddrinfo(ai); + ai = NULL; + } + err = getaddrinfo(host.c_str(), NULL, &hints, &ai); + } + + if (err) { +#if defined(OS_WIN) + err = WSAGetLastError(); +#endif + + // Return the OS error to the caller. + if (os_error) + *os_error = err; + + // If the call to getaddrinfo() failed because of a system error, report + // it separately from ERR_NAME_NOT_RESOLVED. +#if defined(OS_WIN) + if (err != WSAHOST_NOT_FOUND && err != WSANO_DATA) + return ERR_NAME_RESOLUTION_FAILED; +#elif defined(OS_POSIX) && !defined(OS_FREEBSD) + if (err != EAI_NONAME && err != EAI_NODATA) + return ERR_NAME_RESOLUTION_FAILED; +#endif + + return ERR_NAME_NOT_RESOLVED; + } + +#if defined(OS_ANDROID) + // Workaround for Android's getaddrinfo leaving ai==NULL without an error. + // http://crbug.com/134142 + if (ai == NULL) + return ERR_NAME_NOT_RESOLVED; +#endif + + *addrlist = AddressList::CreateFromAddrinfo(ai); + freeaddrinfo(ai); + return OK; +} + +} // namespace net diff --git a/net/dns/host_resolver_proc.h b/net/dns/host_resolver_proc.h new file mode 100644 index 0000000..b4fc0fa --- /dev/null +++ b/net/dns/host_resolver_proc.h @@ -0,0 +1,96 @@ +// 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. + +#ifndef NET_DNS_HOST_RESOLVER_PROC_H_ +#define NET_DNS_HOST_RESOLVER_PROC_H_ + +#include <string> + +#include "base/memory/ref_counted.h" +#include "net/base/address_family.h" +#include "net/base/net_export.h" + +namespace net { + +class AddressList; + +// Interface for a getaddrinfo()-like procedure. This is used by unit-tests +// to control the underlying resolutions in HostResolverImpl. HostResolverProcs +// can be chained together; they fallback to the next procedure in the chain +// by calling ResolveUsingPrevious(). +// +// Note that implementations of HostResolverProc *MUST BE THREADSAFE*, since +// the HostResolver implementation using them can be multi-threaded. +class NET_EXPORT HostResolverProc + : public base::RefCountedThreadSafe<HostResolverProc> { + public: + explicit HostResolverProc(HostResolverProc* previous); + + // Resolves |host| to an address list, restricting the results to addresses + // in |address_family|. If successful returns OK and fills |addrlist| with + // a list of socket addresses. Otherwise returns a network error code, and + // fills |os_error| with a more specific error if it was non-NULL. + virtual int Resolve(const std::string& host, + AddressFamily address_family, + HostResolverFlags host_resolver_flags, + AddressList* addrlist, + int* os_error) = 0; + + protected: + friend class base::RefCountedThreadSafe<HostResolverProc>; + + virtual ~HostResolverProc(); + + // Asks the fallback procedure (if set) to do the resolve. + int ResolveUsingPrevious(const std::string& host, + AddressFamily address_family, + HostResolverFlags host_resolver_flags, + AddressList* addrlist, + int* os_error); + + private: + friend class HostResolverImpl; + friend class MockHostResolverBase; + friend class ScopedDefaultHostResolverProc; + + // Sets the previous procedure in the chain. Aborts if this would result in a + // cycle. + void SetPreviousProc(HostResolverProc* proc); + + // Sets the last procedure in the chain, i.e. appends |proc| to the end of the + // current chain. Aborts if this would result in a cycle. + void SetLastProc(HostResolverProc* proc); + + // Returns the last procedure in the chain starting at |proc|. Will return + // NULL iff |proc| is NULL. + static HostResolverProc* GetLastProc(HostResolverProc* proc); + + // Sets the default host resolver procedure that is used by HostResolverImpl. + // This can be used through ScopedDefaultHostResolverProc to set a catch-all + // DNS block in unit-tests (individual tests should use MockHostResolver to + // prevent hitting the network). + static HostResolverProc* SetDefault(HostResolverProc* proc); + static HostResolverProc* GetDefault(); + + scoped_refptr<HostResolverProc> previous_proc_; + static HostResolverProc* default_proc_; + + DISALLOW_COPY_AND_ASSIGN(HostResolverProc); +}; + +// Resolves |host| to an address list, using the system's default host resolver. +// (i.e. this calls out to getaddrinfo()). If successful returns OK and fills +// |addrlist| with a list of socket addresses. Otherwise returns a +// network error code, and fills |os_error| with a more specific error if it +// was non-NULL. +NET_EXPORT_PRIVATE int SystemHostResolverProc( + const std::string& host, + AddressFamily address_family, + HostResolverFlags host_resolver_flags, + AddressList* addrlist, + int* os_error); + +} // namespace net + +#endif // NET_DNS_HOST_RESOLVER_PROC_H_ diff --git a/net/dns/mapped_host_resolver.cc b/net/dns/mapped_host_resolver.cc new file mode 100644 index 0000000..c905b34 --- /dev/null +++ b/net/dns/mapped_host_resolver.cc @@ -0,0 +1,67 @@ +// 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/mapped_host_resolver.h" + +#include "base/string_util.h" +#include "net/base/host_port_pair.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" + +namespace net { + +MappedHostResolver::MappedHostResolver(scoped_ptr<HostResolver> impl) + : impl_(impl.Pass()) { +} + +MappedHostResolver::~MappedHostResolver() { +} + +int MappedHostResolver::Resolve(const RequestInfo& original_info, + AddressList* addresses, + const CompletionCallback& callback, + RequestHandle* out_req, + const BoundNetLog& net_log) { + RequestInfo info = original_info; + int rv = ApplyRules(&info); + if (rv != OK) + return rv; + + return impl_->Resolve(info, addresses, callback, out_req, net_log); +} + +int MappedHostResolver::ResolveFromCache(const RequestInfo& original_info, + AddressList* addresses, + const BoundNetLog& net_log) { + RequestInfo info = original_info; + int rv = ApplyRules(&info); + if (rv != OK) + return rv; + + return impl_->ResolveFromCache(info, addresses, net_log); +} + +void MappedHostResolver::CancelRequest(RequestHandle req) { + impl_->CancelRequest(req); +} + +void MappedHostResolver::ProbeIPv6Support() { + impl_->ProbeIPv6Support(); +} + +HostCache* MappedHostResolver::GetHostCache() { + return impl_->GetHostCache(); +} + +int MappedHostResolver::ApplyRules(RequestInfo* info) const { + HostPortPair host_port(info->host_port_pair()); + if (rules_.RewriteHost(&host_port)) { + if (host_port.host() == "~NOTFOUND") + return ERR_NAME_NOT_RESOLVED; + info->set_host_port_pair(host_port); + } + return OK; +} + +} // namespace net diff --git a/net/dns/mapped_host_resolver.h b/net/dns/mapped_host_resolver.h new file mode 100644 index 0000000..db64689 --- /dev/null +++ b/net/dns/mapped_host_resolver.h @@ -0,0 +1,72 @@ +// 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. + +#ifndef NET_DNS_MAPPED_HOST_RESOLVER_H_ +#define NET_DNS_MAPPED_HOST_RESOLVER_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "net/base/host_mapping_rules.h" +#include "net/base/net_export.h" +#include "net/dns/host_resolver.h" + +namespace net { + +// This class wraps an existing HostResolver instance, but modifies the +// request before passing it off to |impl|. This is different from +// MockHostResolver which does the remapping at the HostResolverProc +// layer, so it is able to preserve the effectiveness of the cache. +class NET_EXPORT MappedHostResolver : public HostResolver { + public: + // Creates a MappedHostResolver that forwards all of its requests through + // |impl|. + explicit MappedHostResolver(scoped_ptr<HostResolver> impl); + virtual ~MappedHostResolver(); + + // Adds a rule to this mapper. The format of the rule can be one of: + // + // "MAP" <hostname_pattern> <replacement_host> [":" <replacement_port>] + // "EXCLUDE" <hostname_pattern> + // + // The <replacement_host> can be either a hostname, or an IP address literal, + // or "~NOTFOUND". If it is "~NOTFOUND" then all matched hostnames will fail + // to be resolved with ERR_NAME_NOT_RESOLVED. + // + // Returns true if the rule was successfully parsed and added. + bool AddRuleFromString(const std::string& rule_string) { + return rules_.AddRuleFromString(rule_string); + } + + // Takes a comma separated list of rules, and assigns them to this resolver. + void SetRulesFromString(const std::string& rules_string) { + rules_.SetRulesFromString(rules_string); + } + + // HostResolver methods: + virtual int Resolve(const RequestInfo& info, + AddressList* addresses, + const CompletionCallback& callback, + RequestHandle* out_req, + const BoundNetLog& net_log) OVERRIDE; + virtual int ResolveFromCache(const RequestInfo& info, + AddressList* addresses, + const BoundNetLog& net_log) OVERRIDE; + virtual void CancelRequest(RequestHandle req) OVERRIDE; + virtual void ProbeIPv6Support() OVERRIDE; + virtual HostCache* GetHostCache() OVERRIDE; + + private: + // Modify the request |info| according to |rules_|. Returns either OK or + // the network error code that the hostname's resolution mapped to. + int ApplyRules(RequestInfo* info) const; + + scoped_ptr<HostResolver> impl_; + + HostMappingRules rules_; +}; + +} // namespace net + +#endif // NET_DNS_MAPPED_HOST_RESOLVER_H_ diff --git a/net/dns/mapped_host_resolver_unittest.cc b/net/dns/mapped_host_resolver_unittest.cc new file mode 100644 index 0000000..8da95e1 --- /dev/null +++ b/net/dns/mapped_host_resolver_unittest.cc @@ -0,0 +1,219 @@ +// 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/mapped_host_resolver.h" + +#include "net/base/address_list.h" +#include "net/base/net_errors.h" +#include "net/base/net_log.h" +#include "net/base/net_util.h" +#include "net/base/test_completion_callback.h" +#include "net/dns/mock_host_resolver.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +std::string FirstAddress(const AddressList& address_list) { + if (address_list.empty()) + return ""; + return address_list.front().ToString(); +} + +TEST(MappedHostResolverTest, Inclusion) { + // Create a mock host resolver, with specific hostname to IP mappings. + scoped_ptr<MockHostResolver> resolver_impl(new MockHostResolver()); + resolver_impl->rules()->AddSimulatedFailure("*google.com"); + resolver_impl->rules()->AddRule("baz.com", "192.168.1.5"); + resolver_impl->rules()->AddRule("foo.com", "192.168.1.8"); + resolver_impl->rules()->AddRule("proxy", "192.168.1.11"); + + // Create a remapped resolver that uses |resolver_impl|. + scoped_ptr<MappedHostResolver> resolver( + new MappedHostResolver(resolver_impl.PassAs<HostResolver>())); + + int rv; + AddressList address_list; + + // Try resolving "www.google.com:80". There are no mappings yet, so this + // hits |resolver_impl| and fails. + TestCompletionCallback callback; + rv = resolver->Resolve(HostResolver::RequestInfo( + HostPortPair("www.google.com", 80)), + &address_list, callback.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_NAME_NOT_RESOLVED, rv); + + // Remap *.google.com to baz.com. + EXPECT_TRUE(resolver->AddRuleFromString("map *.google.com baz.com")); + + // Try resolving "www.google.com:80". Should be remapped to "baz.com:80". + rv = resolver->Resolve(HostResolver::RequestInfo( + HostPortPair("www.google.com", 80)), + &address_list, callback.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_EQ("192.168.1.5:80", FirstAddress(address_list)); + + // Try resolving "foo.com:77". This will NOT be remapped, so result + // is "foo.com:77". + rv = resolver->Resolve(HostResolver::RequestInfo(HostPortPair("foo.com", 77)), + &address_list, callback.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_EQ("192.168.1.8:77", FirstAddress(address_list)); + + // Remap "*.org" to "proxy:99". + EXPECT_TRUE(resolver->AddRuleFromString("Map *.org proxy:99")); + + // Try resolving "chromium.org:61". Should be remapped to "proxy:99". + rv = resolver->Resolve(HostResolver::RequestInfo + (HostPortPair("chromium.org", 61)), + &address_list, callback.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_EQ("192.168.1.11:99", FirstAddress(address_list)); +} + +// Tests that exclusions are respected. +TEST(MappedHostResolverTest, Exclusion) { + // Create a mock host resolver, with specific hostname to IP mappings. + scoped_ptr<MockHostResolver> resolver_impl(new MockHostResolver()); + resolver_impl->rules()->AddRule("baz", "192.168.1.5"); + resolver_impl->rules()->AddRule("www.google.com", "192.168.1.3"); + + // Create a remapped resolver that uses |resolver_impl|. + scoped_ptr<MappedHostResolver> resolver( + new MappedHostResolver(resolver_impl.PassAs<HostResolver>())); + + int rv; + AddressList address_list; + TestCompletionCallback callback; + + // Remap "*.com" to "baz". + EXPECT_TRUE(resolver->AddRuleFromString("map *.com baz")); + + // Add an exclusion for "*.google.com". + EXPECT_TRUE(resolver->AddRuleFromString("EXCLUDE *.google.com")); + + // Try resolving "www.google.com". Should not be remapped due to exclusion). + rv = resolver->Resolve(HostResolver::RequestInfo( + HostPortPair("www.google.com", 80)), + &address_list, callback.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_EQ("192.168.1.3:80", FirstAddress(address_list)); + + // Try resolving "chrome.com:80". Should be remapped to "baz:80". + rv = resolver->Resolve(HostResolver::RequestInfo( + HostPortPair("chrome.com", 80)), + &address_list, callback.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_EQ("192.168.1.5:80", FirstAddress(address_list)); +} + +TEST(MappedHostResolverTest, SetRulesFromString) { + // Create a mock host resolver, with specific hostname to IP mappings. + scoped_ptr<MockHostResolver> resolver_impl(new MockHostResolver()); + resolver_impl->rules()->AddRule("baz", "192.168.1.7"); + resolver_impl->rules()->AddRule("bar", "192.168.1.9"); + + // Create a remapped resolver that uses |resolver_impl|. + scoped_ptr<MappedHostResolver> resolver( + new MappedHostResolver(resolver_impl.PassAs<HostResolver>())); + + int rv; + AddressList address_list; + TestCompletionCallback callback; + + // Remap "*.com" to "baz", and *.net to "bar:60". + resolver->SetRulesFromString("map *.com baz , map *.net bar:60"); + + // Try resolving "www.google.com". Should be remapped to "baz". + rv = resolver->Resolve(HostResolver::RequestInfo( + HostPortPair("www.google.com", 80)), + &address_list, callback.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_EQ("192.168.1.7:80", FirstAddress(address_list)); + + // Try resolving "chrome.net:80". Should be remapped to "bar:60". + rv = resolver->Resolve(HostResolver::RequestInfo( + HostPortPair("chrome.net", 80)), + &address_list, callback.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_EQ("192.168.1.9:60", FirstAddress(address_list)); +} + +// Parsing bad rules should silently discard the rule (and never crash). +TEST(MappedHostResolverTest, ParseInvalidRules) { + scoped_ptr<MappedHostResolver> resolver( + new MappedHostResolver(scoped_ptr<HostResolver>())); + + EXPECT_FALSE(resolver->AddRuleFromString("xyz")); + EXPECT_FALSE(resolver->AddRuleFromString("")); + EXPECT_FALSE(resolver->AddRuleFromString(" ")); + EXPECT_FALSE(resolver->AddRuleFromString("EXCLUDE")); + EXPECT_FALSE(resolver->AddRuleFromString("EXCLUDE foo bar")); + EXPECT_FALSE(resolver->AddRuleFromString("INCLUDE")); + EXPECT_FALSE(resolver->AddRuleFromString("INCLUDE x")); + EXPECT_FALSE(resolver->AddRuleFromString("INCLUDE x :10")); +} + +// Test mapping hostnames to resolving failures. +TEST(MappedHostResolverTest, MapToError) { + scoped_ptr<MockHostResolver> resolver_impl(new MockHostResolver()); + resolver_impl->rules()->AddRule("*", "192.168.1.5"); + + scoped_ptr<MappedHostResolver> resolver( + new MappedHostResolver(resolver_impl.PassAs<HostResolver>())); + + int rv; + AddressList address_list; + + // Remap *.google.com to resolving failures. + EXPECT_TRUE(resolver->AddRuleFromString("MAP *.google.com ~NOTFOUND")); + + // Try resolving www.google.com --> Should give an error. + TestCompletionCallback callback1; + rv = resolver->Resolve(HostResolver::RequestInfo( + HostPortPair("www.google.com", 80)), + &address_list, callback1.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_NAME_NOT_RESOLVED, rv); + + // Try resolving www.foo.com --> Should succeed. + TestCompletionCallback callback2; + rv = resolver->Resolve(HostResolver::RequestInfo( + HostPortPair("www.foo.com", 80)), + &address_list, callback2.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_EQ("192.168.1.5:80", FirstAddress(address_list)); +} + +} // namespace + +} // namespace net diff --git a/net/dns/mock_host_resolver.cc b/net/dns/mock_host_resolver.cc new file mode 100644 index 0000000..fb03836 --- /dev/null +++ b/net/dns/mock_host_resolver.cc @@ -0,0 +1,417 @@ +// 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/mock_host_resolver.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop.h" +#include "base/stl_util.h" +#include "base/string_util.h" +#include "base/strings/string_split.h" +#include "base/threading/platform_thread.h" +#include "net/base/host_cache.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/base/test_completion_callback.h" +#if defined(OS_WIN) +#include "net/base/winsock_init.h" +#endif + +namespace net { + +namespace { + +// Cache size for the MockCachingHostResolver. +const unsigned kMaxCacheEntries = 100; +// TTL for the successful resolutions. Failures are not cached. +const unsigned kCacheEntryTTLSeconds = 60; + +} // namespace + +int ParseAddressList(const std::string& host_list, + const std::string& canonical_name, + AddressList* addrlist) { + *addrlist = AddressList(); + std::vector<std::string> addresses; + base::SplitString(host_list, ',', &addresses); + addrlist->set_canonical_name(canonical_name); + for (size_t index = 0; index < addresses.size(); ++index) { + IPAddressNumber ip_number; + if (!ParseIPLiteralToNumber(addresses[index], &ip_number)) { + LOG(WARNING) << "Not a supported IP literal: " << addresses[index]; + return ERR_UNEXPECTED; + } + addrlist->push_back(IPEndPoint(ip_number, -1)); + } + return OK; +} + +struct MockHostResolverBase::Request { + Request(const RequestInfo& req_info, + AddressList* addr, + const CompletionCallback& cb) + : info(req_info), addresses(addr), callback(cb) {} + RequestInfo info; + AddressList* addresses; + CompletionCallback callback; +}; + +MockHostResolverBase::~MockHostResolverBase() { + STLDeleteValues(&requests_); +} + +int MockHostResolverBase::Resolve(const RequestInfo& info, + AddressList* addresses, + const CompletionCallback& callback, + RequestHandle* handle, + const BoundNetLog& net_log) { + DCHECK(CalledOnValidThread()); + num_resolve_++; + size_t id = next_request_id_++; + int rv = ResolveFromIPLiteralOrCache(info, addresses); + if (rv != ERR_DNS_CACHE_MISS) { + return rv; + } + if (synchronous_mode_) { + return ResolveProc(id, info, addresses); + } + // Store the request for asynchronous resolution + Request* req = new Request(info, addresses, callback); + requests_[id] = req; + if (handle) + *handle = reinterpret_cast<RequestHandle>(id); + + if (!ondemand_mode_) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&MockHostResolverBase::ResolveNow, AsWeakPtr(), id)); + } + + return ERR_IO_PENDING; +} + +int MockHostResolverBase::ResolveFromCache(const RequestInfo& info, + AddressList* addresses, + const BoundNetLog& net_log) { + num_resolve_from_cache_++; + DCHECK(CalledOnValidThread()); + next_request_id_++; + int rv = ResolveFromIPLiteralOrCache(info, addresses); + return rv; +} + +void MockHostResolverBase::CancelRequest(RequestHandle handle) { + DCHECK(CalledOnValidThread()); + size_t id = reinterpret_cast<size_t>(handle); + RequestMap::iterator it = requests_.find(id); + if (it != requests_.end()) { + scoped_ptr<Request> req(it->second); + requests_.erase(it); + } else { + NOTREACHED() << "CancelRequest must NOT be called after request is " + "complete or canceled."; + } +} + +HostCache* MockHostResolverBase::GetHostCache() { + return cache_.get(); +} + +void MockHostResolverBase::ResolveAllPending() { + DCHECK(CalledOnValidThread()); + DCHECK(ondemand_mode_); + for (RequestMap::iterator i = requests_.begin(); i != requests_.end(); ++i) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&MockHostResolverBase::ResolveNow, AsWeakPtr(), i->first)); + } +} + +// start id from 1 to distinguish from NULL RequestHandle +MockHostResolverBase::MockHostResolverBase(bool use_caching) + : synchronous_mode_(false), + ondemand_mode_(false), + next_request_id_(1), + num_resolve_(0), + num_resolve_from_cache_(0) { + rules_ = CreateCatchAllHostResolverProc(); + + if (use_caching) { + cache_.reset(new HostCache(kMaxCacheEntries)); + } +} + +int MockHostResolverBase::ResolveFromIPLiteralOrCache(const RequestInfo& info, + AddressList* addresses) { + IPAddressNumber ip; + if (ParseIPLiteralToNumber(info.hostname(), &ip)) { + *addresses = AddressList::CreateFromIPAddress(ip, info.port()); + if (info.host_resolver_flags() & HOST_RESOLVER_CANONNAME) + addresses->SetDefaultCanonicalName(); + return OK; + } + int rv = ERR_DNS_CACHE_MISS; + if (cache_.get() && info.allow_cached_response()) { + HostCache::Key key(info.hostname(), + info.address_family(), + info.host_resolver_flags()); + const HostCache::Entry* entry = cache_->Lookup(key, base::TimeTicks::Now()); + if (entry) { + rv = entry->error; + if (rv == OK) + *addresses = AddressList::CopyWithPort(entry->addrlist, info.port()); + } + } + return rv; +} + +int MockHostResolverBase::ResolveProc(size_t id, + const RequestInfo& info, + AddressList* addresses) { + AddressList addr; + int rv = rules_->Resolve(info.hostname(), + info.address_family(), + info.host_resolver_flags(), + &addr, + NULL); + if (cache_.get()) { + HostCache::Key key(info.hostname(), + info.address_family(), + info.host_resolver_flags()); + // Storing a failure with TTL 0 so that it overwrites previous value. + base::TimeDelta ttl; + if (rv == OK) + ttl = base::TimeDelta::FromSeconds(kCacheEntryTTLSeconds); + cache_->Set(key, HostCache::Entry(rv, addr), base::TimeTicks::Now(), ttl); + } + if (rv == OK) + *addresses = AddressList::CopyWithPort(addr, info.port()); + return rv; +} + +void MockHostResolverBase::ResolveNow(size_t id) { + RequestMap::iterator it = requests_.find(id); + if (it == requests_.end()) + return; // was canceled + + scoped_ptr<Request> req(it->second); + requests_.erase(it); + int rv = ResolveProc(id, req->info, req->addresses); + if (!req->callback.is_null()) + req->callback.Run(rv); +} + +//----------------------------------------------------------------------------- + +struct RuleBasedHostResolverProc::Rule { + enum ResolverType { + kResolverTypeFail, + kResolverTypeSystem, + kResolverTypeIPLiteral, + }; + + ResolverType resolver_type; + std::string host_pattern; + AddressFamily address_family; + HostResolverFlags host_resolver_flags; + std::string replacement; + std::string canonical_name; + int latency_ms; // In milliseconds. + + Rule(ResolverType resolver_type, + const std::string& host_pattern, + AddressFamily address_family, + HostResolverFlags host_resolver_flags, + const std::string& replacement, + const std::string& canonical_name, + int latency_ms) + : resolver_type(resolver_type), + host_pattern(host_pattern), + address_family(address_family), + host_resolver_flags(host_resolver_flags), + replacement(replacement), + canonical_name(canonical_name), + latency_ms(latency_ms) {} +}; + +RuleBasedHostResolverProc::RuleBasedHostResolverProc(HostResolverProc* previous) + : HostResolverProc(previous) { +} + +void RuleBasedHostResolverProc::AddRule(const std::string& host_pattern, + const std::string& replacement) { + AddRuleForAddressFamily(host_pattern, ADDRESS_FAMILY_UNSPECIFIED, + replacement); +} + +void RuleBasedHostResolverProc::AddRuleForAddressFamily( + const std::string& host_pattern, + AddressFamily address_family, + const std::string& replacement) { + DCHECK(!replacement.empty()); + HostResolverFlags flags = HOST_RESOLVER_LOOPBACK_ONLY | + HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6; + Rule rule(Rule::kResolverTypeSystem, host_pattern, address_family, flags, + replacement, "", 0); + rules_.push_back(rule); +} + +void RuleBasedHostResolverProc::AddIPLiteralRule( + const std::string& host_pattern, + const std::string& ip_literal, + const std::string& canonical_name) { + // Literals are always resolved to themselves by HostResolverImpl, + // consequently we do not support remapping them. + IPAddressNumber ip_number; + DCHECK(!ParseIPLiteralToNumber(host_pattern, &ip_number)); + HostResolverFlags flags = HOST_RESOLVER_LOOPBACK_ONLY | + HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6; + if (!canonical_name.empty()) + flags |= HOST_RESOLVER_CANONNAME; + Rule rule(Rule::kResolverTypeIPLiteral, host_pattern, + ADDRESS_FAMILY_UNSPECIFIED, flags, ip_literal, canonical_name, + 0); + rules_.push_back(rule); +} + +void RuleBasedHostResolverProc::AddRuleWithLatency( + const std::string& host_pattern, + const std::string& replacement, + int latency_ms) { + DCHECK(!replacement.empty()); + HostResolverFlags flags = HOST_RESOLVER_LOOPBACK_ONLY | + HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6; + Rule rule(Rule::kResolverTypeSystem, host_pattern, ADDRESS_FAMILY_UNSPECIFIED, + flags, replacement, "", latency_ms); + rules_.push_back(rule); +} + +void RuleBasedHostResolverProc::AllowDirectLookup( + const std::string& host_pattern) { + HostResolverFlags flags = HOST_RESOLVER_LOOPBACK_ONLY | + HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6; + Rule rule(Rule::kResolverTypeSystem, host_pattern, ADDRESS_FAMILY_UNSPECIFIED, + flags, "", "", 0); + rules_.push_back(rule); +} + +void RuleBasedHostResolverProc::AddSimulatedFailure( + const std::string& host_pattern) { + HostResolverFlags flags = HOST_RESOLVER_LOOPBACK_ONLY | + HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6; + Rule rule(Rule::kResolverTypeFail, host_pattern, ADDRESS_FAMILY_UNSPECIFIED, + flags, "", "", 0); + rules_.push_back(rule); +} + +void RuleBasedHostResolverProc::ClearRules() { + rules_.clear(); +} + +int RuleBasedHostResolverProc::Resolve(const std::string& host, + AddressFamily address_family, + HostResolverFlags host_resolver_flags, + AddressList* addrlist, + int* os_error) { + RuleList::iterator r; + for (r = rules_.begin(); r != rules_.end(); ++r) { + bool matches_address_family = + r->address_family == ADDRESS_FAMILY_UNSPECIFIED || + r->address_family == address_family; + // Flags match if all of the bitflags in host_resolver_flags are enabled + // in the rule's host_resolver_flags. However, the rule may have additional + // flags specified, in which case the flags should still be considered a + // match. + bool matches_flags = (r->host_resolver_flags & host_resolver_flags) == + host_resolver_flags; + if (matches_flags && matches_address_family && + MatchPattern(host, r->host_pattern)) { + if (r->latency_ms != 0) { + base::PlatformThread::Sleep( + base::TimeDelta::FromMilliseconds(r->latency_ms)); + } + + // Remap to a new host. + const std::string& effective_host = + r->replacement.empty() ? host : r->replacement; + + // Apply the resolving function to the remapped hostname. + switch (r->resolver_type) { + case Rule::kResolverTypeFail: + return ERR_NAME_NOT_RESOLVED; + case Rule::kResolverTypeSystem: +#if defined(OS_WIN) + net::EnsureWinsockInit(); +#endif + return SystemHostResolverProc(effective_host, + address_family, + host_resolver_flags, + addrlist, os_error); + case Rule::kResolverTypeIPLiteral: + return ParseAddressList(effective_host, + r->canonical_name, + addrlist); + default: + NOTREACHED(); + return ERR_UNEXPECTED; + } + } + } + return ResolveUsingPrevious(host, address_family, + host_resolver_flags, addrlist, os_error); +} + +RuleBasedHostResolverProc::~RuleBasedHostResolverProc() { +} + +RuleBasedHostResolverProc* CreateCatchAllHostResolverProc() { + RuleBasedHostResolverProc* catchall = new RuleBasedHostResolverProc(NULL); + catchall->AddIPLiteralRule("*", "127.0.0.1", "localhost"); + + // Next add a rules-based layer the use controls. + return new RuleBasedHostResolverProc(catchall); +} + +//----------------------------------------------------------------------------- + +int HangingHostResolver::Resolve(const RequestInfo& info, + AddressList* addresses, + const CompletionCallback& callback, + RequestHandle* out_req, + const BoundNetLog& net_log) { + return ERR_IO_PENDING; +} + +int HangingHostResolver::ResolveFromCache(const RequestInfo& info, + AddressList* addresses, + const BoundNetLog& net_log) { + return ERR_DNS_CACHE_MISS; +} + +//----------------------------------------------------------------------------- + +ScopedDefaultHostResolverProc::ScopedDefaultHostResolverProc() {} + +ScopedDefaultHostResolverProc::ScopedDefaultHostResolverProc( + HostResolverProc* proc) { + Init(proc); +} + +ScopedDefaultHostResolverProc::~ScopedDefaultHostResolverProc() { + HostResolverProc* old_proc = HostResolverProc::SetDefault(previous_proc_); + // The lifetimes of multiple instances must be nested. + CHECK_EQ(old_proc, current_proc_); +} + +void ScopedDefaultHostResolverProc::Init(HostResolverProc* proc) { + current_proc_ = proc; + previous_proc_ = HostResolverProc::SetDefault(current_proc_); + current_proc_->SetLastProc(previous_proc_); +} + +} // namespace net diff --git a/net/dns/mock_host_resolver.h b/net/dns/mock_host_resolver.h new file mode 100644 index 0000000..d22e800c --- /dev/null +++ b/net/dns/mock_host_resolver.h @@ -0,0 +1,254 @@ +// 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_MOCK_HOST_RESOLVER_H_ +#define NET_DNS_MOCK_HOST_RESOLVER_H_ + +#include <list> +#include <map> + +#include "base/memory/weak_ptr.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/non_thread_safe.h" +#include "net/dns/host_resolver.h" +#include "net/dns/host_resolver_proc.h" + +namespace net { + +class HostCache; +class RuleBasedHostResolverProc; + +// Fills |*addrlist| with a socket address for |host_list| which should be a +// comma-separated list of IPv4 or IPv6 literal(s) without enclosing brackets. +// If |canonical_name| is non-empty it is used as the DNS canonical name for +// the host. Returns OK on success, ERR_UNEXPECTED otherwise. +int ParseAddressList(const std::string& host_list, + const std::string& canonical_name, + AddressList* addrlist); + +// In most cases, it is important that unit tests avoid relying on making actual +// DNS queries since the resulting tests can be flaky, especially if the network +// is unreliable for some reason. To simplify writing tests that avoid making +// actual DNS queries, pass a MockHostResolver as the HostResolver dependency. +// The socket addresses returned can be configured using the +// RuleBasedHostResolverProc: +// +// host_resolver->rules()->AddRule("foo.com", "1.2.3.4"); +// host_resolver->rules()->AddRule("bar.com", "2.3.4.5"); +// +// The above rules define a static mapping from hostnames to IP address +// literals. The first parameter to AddRule specifies a host pattern to match +// against, and the second parameter indicates what value should be used to +// replace the given hostname. So, the following is also supported: +// +// host_mapper->AddRule("*.com", "127.0.0.1"); +// +// Replacement doesn't have to be string representing an IP address. It can +// re-map one hostname to another as well. +// +// By default, MockHostResolvers include a single rule that maps all hosts to +// 127.0.0.1. + +// Base class shared by MockHostResolver and MockCachingHostResolver. +class MockHostResolverBase : public HostResolver, + public base::SupportsWeakPtr<MockHostResolverBase>, + public base::NonThreadSafe { + public: + virtual ~MockHostResolverBase(); + + RuleBasedHostResolverProc* rules() { return rules_; } + void set_rules(RuleBasedHostResolverProc* rules) { rules_ = rules; } + + // Controls whether resolutions complete synchronously or asynchronously. + void set_synchronous_mode(bool is_synchronous) { + synchronous_mode_ = is_synchronous; + } + + // Asynchronous requests are automatically resolved by default. + // If set_ondemand_mode() is set then Resolve() returns IO_PENDING and + // ResolveAllPending() must be explicitly invoked to resolve all requests + // that are pending. + void set_ondemand_mode(bool is_ondemand) { + ondemand_mode_ = is_ondemand; + } + + // HostResolver methods: + virtual int Resolve(const RequestInfo& info, + AddressList* addresses, + const CompletionCallback& callback, + RequestHandle* out_req, + const BoundNetLog& net_log) OVERRIDE; + virtual int ResolveFromCache(const RequestInfo& info, + AddressList* addresses, + const BoundNetLog& net_log) OVERRIDE; + virtual void CancelRequest(RequestHandle req) OVERRIDE; + virtual HostCache* GetHostCache() OVERRIDE; + + // Resolves all pending requests. It is only valid to invoke this if + // set_ondemand_mode was set before. The requests are resolved asynchronously, + // after this call returns. + void ResolveAllPending(); + + // Returns true if there are pending requests that can be resolved by invoking + // ResolveAllPending(). + bool has_pending_requests() const { return !requests_.empty(); } + + // The number of times that Resolve() has been called. + size_t num_resolve() const { + return num_resolve_; + } + + // The number of times that ResolveFromCache() has been called. + size_t num_resolve_from_cache() const { + return num_resolve_from_cache_; + } + + protected: + explicit MockHostResolverBase(bool use_caching); + + private: + struct Request; + typedef std::map<size_t, Request*> RequestMap; + + // Resolve as IP or from |cache_| return cached error or + // DNS_CACHE_MISS if failed. + int ResolveFromIPLiteralOrCache(const RequestInfo& info, + AddressList* addresses); + // Resolve via |proc_|. + int ResolveProc(size_t id, const RequestInfo& info, AddressList* addresses); + // Resolve request stored in |requests_|. Pass rv to callback. + void ResolveNow(size_t id); + + bool synchronous_mode_; + bool ondemand_mode_; + scoped_refptr<RuleBasedHostResolverProc> rules_; + scoped_ptr<HostCache> cache_; + RequestMap requests_; + size_t next_request_id_; + + size_t num_resolve_; + size_t num_resolve_from_cache_; + + DISALLOW_COPY_AND_ASSIGN(MockHostResolverBase); +}; + +class MockHostResolver : public MockHostResolverBase { + public: + MockHostResolver() : MockHostResolverBase(false /*use_caching*/) {} + virtual ~MockHostResolver() {} +}; + +// Same as MockHostResolver, except internally it uses a host-cache. +// +// Note that tests are advised to use MockHostResolver instead, since it is +// more predictable. (MockHostResolver also can be put into synchronous +// operation mode in case that is what you needed from the caching version). +class MockCachingHostResolver : public MockHostResolverBase { + public: + MockCachingHostResolver() : MockHostResolverBase(true /*use_caching*/) {} + virtual ~MockCachingHostResolver() {} +}; + +// RuleBasedHostResolverProc applies a set of rules to map a host string to +// a replacement host string. It then uses the system host resolver to return +// a socket address. Generally the replacement should be an IPv4 literal so +// there is no network dependency. +class RuleBasedHostResolverProc : public HostResolverProc { + public: + explicit RuleBasedHostResolverProc(HostResolverProc* previous); + + // Any hostname matching the given pattern will be replaced with the given + // replacement value. Usually, replacement should be an IP address literal. + void AddRule(const std::string& host_pattern, + const std::string& replacement); + + // Same as AddRule(), but further restricts to |address_family|. + void AddRuleForAddressFamily(const std::string& host_pattern, + AddressFamily address_family, + const std::string& replacement); + + // Same as AddRule(), but the replacement is expected to be an IPv4 or IPv6 + // literal. This can be used in place of AddRule() to bypass the system's + // host resolver (the address list will be constructed manually). + // If |canonical_name| is non-empty, it is copied to the resulting AddressList + // but does not impact DNS resolution. + // |ip_literal| can be a single IP address like "192.168.1.1" or a comma + // separated list of IP addresses, like "::1,192:168.1.2". + void AddIPLiteralRule(const std::string& host_pattern, + const std::string& ip_literal, + const std::string& canonical_name); + + void AddRuleWithLatency(const std::string& host_pattern, + const std::string& replacement, + int latency_ms); + + // Make sure that |host| will not be re-mapped or even processed by underlying + // host resolver procedures. It can also be a pattern. + void AllowDirectLookup(const std::string& host); + + // Simulate a lookup failure for |host| (it also can be a pattern). + void AddSimulatedFailure(const std::string& host); + + // Deletes all the rules that have been added. + void ClearRules(); + + // HostResolverProc methods: + virtual int Resolve(const std::string& host, + AddressFamily address_family, + HostResolverFlags host_resolver_flags, + AddressList* addrlist, + int* os_error) OVERRIDE; + + private: + struct Rule; + typedef std::list<Rule> RuleList; + + virtual ~RuleBasedHostResolverProc(); + + RuleList rules_; +}; + +// Create rules that map all requests to localhost. +RuleBasedHostResolverProc* CreateCatchAllHostResolverProc(); + +// HangingHostResolver never completes its |Resolve| request. +class HangingHostResolver : public HostResolver { + public: + virtual int Resolve(const RequestInfo& info, + AddressList* addresses, + const CompletionCallback& callback, + RequestHandle* out_req, + const BoundNetLog& net_log) OVERRIDE; + virtual int ResolveFromCache(const RequestInfo& info, + AddressList* addresses, + const BoundNetLog& net_log) OVERRIDE; + virtual void CancelRequest(RequestHandle req) OVERRIDE {} +}; + +// This class sets the default HostResolverProc for a particular scope. The +// chain of resolver procs starting at |proc| is placed in front of any existing +// default resolver proc(s). This means that if multiple +// ScopedDefaultHostResolverProcs are declared, then resolving will start with +// the procs given to the last-allocated one, then fall back to the procs given +// to the previously-allocated one, and so forth. +// +// NOTE: Only use this as a catch-all safety net. Individual tests should use +// MockHostResolver. +class ScopedDefaultHostResolverProc { + public: + ScopedDefaultHostResolverProc(); + explicit ScopedDefaultHostResolverProc(HostResolverProc* proc); + + ~ScopedDefaultHostResolverProc(); + + void Init(HostResolverProc* proc); + + private: + scoped_refptr<HostResolverProc> current_proc_; + scoped_refptr<HostResolverProc> previous_proc_; +}; + +} // namespace net + +#endif // NET_DNS_MOCK_HOST_RESOLVER_H_ diff --git a/net/dns/single_request_host_resolver.cc b/net/dns/single_request_host_resolver.cc new file mode 100644 index 0000000..e926e86 --- /dev/null +++ b/net/dns/single_request_host_resolver.cc @@ -0,0 +1,77 @@ +// 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/single_request_host_resolver.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "net/base/net_errors.h" + +namespace net { + +SingleRequestHostResolver::SingleRequestHostResolver(HostResolver* resolver) + : resolver_(resolver), + cur_request_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(callback_( + base::Bind(&SingleRequestHostResolver::OnResolveCompletion, + base::Unretained(this)))) { + DCHECK(resolver_ != NULL); +} + +SingleRequestHostResolver::~SingleRequestHostResolver() { + Cancel(); +} + +int SingleRequestHostResolver::Resolve( + const HostResolver::RequestInfo& info, AddressList* addresses, + const CompletionCallback& callback, const BoundNetLog& net_log) { + DCHECK(addresses); + DCHECK_EQ(false, callback.is_null()); + DCHECK(cur_request_callback_.is_null()) << "resolver already in use"; + + HostResolver::RequestHandle request = NULL; + + // We need to be notified of completion before |callback| is called, so that + // we can clear out |cur_request_*|. + CompletionCallback transient_callback = + callback.is_null() ? CompletionCallback() : callback_; + + int rv = resolver_->Resolve( + info, addresses, transient_callback, &request, net_log); + + if (rv == ERR_IO_PENDING) { + DCHECK_EQ(false, callback.is_null()); + // Cleared in OnResolveCompletion(). + cur_request_ = request; + cur_request_callback_ = callback; + } + + return rv; +} + +void SingleRequestHostResolver::Cancel() { + if (!cur_request_callback_.is_null()) { + resolver_->CancelRequest(cur_request_); + cur_request_ = NULL; + cur_request_callback_.Reset(); + } +} + +void SingleRequestHostResolver::OnResolveCompletion(int result) { + DCHECK(cur_request_); + DCHECK_EQ(false, cur_request_callback_.is_null()); + + CompletionCallback callback = cur_request_callback_; + + // Clear the outstanding request information. + cur_request_ = NULL; + cur_request_callback_.Reset(); + + // Call the user's original callback. + callback.Run(result); +} + +} // namespace net diff --git a/net/dns/single_request_host_resolver.h b/net/dns/single_request_host_resolver.h new file mode 100644 index 0000000..52d0132 --- /dev/null +++ b/net/dns/single_request_host_resolver.h @@ -0,0 +1,56 @@ +// 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_SINGLE_REQUEST_HOST_RESOLVER_H_ +#define NET_DNS_SINGLE_REQUEST_HOST_RESOLVER_H_ + +#include "net/dns/host_resolver.h" + +namespace net { + +// This class represents the task of resolving a hostname (or IP address +// literal) to an AddressList object. It wraps HostResolver to resolve only a +// single hostname at a time and cancels this request when going out of scope. +class NET_EXPORT SingleRequestHostResolver { + public: + // |resolver| must remain valid for the lifetime of |this|. + explicit SingleRequestHostResolver(HostResolver* resolver); + + // If a completion callback is pending when the resolver is destroyed, the + // host resolution is cancelled, and the completion callback will not be + // called. + ~SingleRequestHostResolver(); + + // Resolves the given hostname (or IP address literal), filling out the + // |addresses| object upon success. See HostResolver::Resolve() for details. + int Resolve(const HostResolver::RequestInfo& info, + AddressList* addresses, + const CompletionCallback& callback, + const BoundNetLog& net_log); + + // Cancels the in-progress request, if any. This prevents the callback + // from being invoked. Resolve() can be called again after cancelling. + void Cancel(); + + private: + // Callback for when the request to |resolver_| completes, so we dispatch + // to the user's callback. + void OnResolveCompletion(int result); + + // The actual host resolver that will handle the request. + HostResolver* const resolver_; + + // The current request (if any). + HostResolver::RequestHandle cur_request_; + CompletionCallback cur_request_callback_; + + // Completion callback for when request to |resolver_| completes. + CompletionCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(SingleRequestHostResolver); +}; + +} // namespace net + +#endif // NET_DNS_SINGLE_REQUEST_HOST_RESOLVER_H_ diff --git a/net/dns/single_request_host_resolver_unittest.cc b/net/dns/single_request_host_resolver_unittest.cc new file mode 100644 index 0000000..3e2ea1e --- /dev/null +++ b/net/dns/single_request_host_resolver_unittest.cc @@ -0,0 +1,124 @@ +// 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/single_request_host_resolver.h" + +#include "net/base/address_list.h" +#include "net/base/net_errors.h" +#include "net/base/net_log.h" +#include "net/base/test_completion_callback.h" +#include "net/dns/mock_host_resolver.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +// Helper class used by SingleRequestHostResolverTest.Cancel test. +// It checks that only one request is outstanding at a time, and that +// it is cancelled before the class is destroyed. +class HangingHostResolver : public HostResolver { + public: + HangingHostResolver() : outstanding_request_(NULL) {} + + virtual ~HangingHostResolver() { + EXPECT_TRUE(!has_outstanding_request()); + } + + bool has_outstanding_request() const { + return outstanding_request_ != NULL; + } + + virtual int Resolve(const RequestInfo& info, + AddressList* addresses, + const CompletionCallback& callback, + RequestHandle* out_req, + const BoundNetLog& net_log) OVERRIDE { + EXPECT_FALSE(has_outstanding_request()); + outstanding_request_ = reinterpret_cast<RequestHandle>(0x1234); + *out_req = outstanding_request_; + + // Never complete this request! Caller is expected to cancel it + // before destroying the resolver. + return ERR_IO_PENDING; + } + + virtual int ResolveFromCache(const RequestInfo& info, + AddressList* addresses, + const BoundNetLog& net_log) OVERRIDE { + NOTIMPLEMENTED(); + return ERR_UNEXPECTED; + } + + virtual void CancelRequest(RequestHandle req) OVERRIDE { + EXPECT_TRUE(has_outstanding_request()); + EXPECT_EQ(req, outstanding_request_); + outstanding_request_ = NULL; + } + + private: + RequestHandle outstanding_request_; + + DISALLOW_COPY_AND_ASSIGN(HangingHostResolver); +}; + +// Test that a regular end-to-end lookup returns the expected result. +TEST(SingleRequestHostResolverTest, NormalResolve) { + // Create a host resolver dependency that returns address "199.188.1.166" + // for resolutions of "watsup". + MockHostResolver resolver; + resolver.rules()->AddIPLiteralRule("watsup", "199.188.1.166", ""); + + SingleRequestHostResolver single_request_resolver(&resolver); + + // Resolve "watsup:90" using our SingleRequestHostResolver. + AddressList addrlist; + TestCompletionCallback callback; + HostResolver::RequestInfo request(HostPortPair("watsup", 90)); + int rv = single_request_resolver.Resolve( + request, &addrlist, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + // Verify that the result is what we specified in the MockHostResolver. + ASSERT_FALSE(addrlist.empty()); + EXPECT_EQ("199.188.1.166", addrlist.front().ToStringWithoutPort()); +} + +// Test that the Cancel() method cancels any outstanding request. +TEST(SingleRequestHostResolverTest, Cancel) { + HangingHostResolver resolver; + + { + SingleRequestHostResolver single_request_resolver(&resolver); + + // Resolve "watsup:90" using our SingleRequestHostResolver. + AddressList addrlist; + TestCompletionCallback callback; + HostResolver::RequestInfo request(HostPortPair("watsup", 90)); + int rv = single_request_resolver.Resolve( + request, &addrlist, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_TRUE(resolver.has_outstanding_request()); + } + + // Now that the SingleRequestHostResolver has been destroyed, the + // in-progress request should have been aborted. + EXPECT_FALSE(resolver.has_outstanding_request()); +} + +// Test that the Cancel() method is a no-op when there is no outstanding +// request. +TEST(SingleRequestHostResolverTest, CancelWhileNoPendingRequest) { + HangingHostResolver resolver; + SingleRequestHostResolver single_request_resolver(&resolver); + single_request_resolver.Cancel(); + + // To pass, HangingHostResolver should not have received a cancellation + // request (since there is nothing to cancel). If it does, it will crash. +} + +} // namespace + +} // namespace net |