diff options
Diffstat (limited to 'net/base')
-rw-r--r-- | net/base/address_list_unittest.cc | 23 | ||||
-rw-r--r-- | net/base/host_resolver.cc | 608 | ||||
-rw-r--r-- | net/base/host_resolver.h | 159 | ||||
-rw-r--r-- | net/base/host_resolver_impl.cc | 483 | ||||
-rw-r--r-- | net/base/host_resolver_impl.h | 136 | ||||
-rw-r--r-- | net/base/host_resolver_impl_unittest.cc (renamed from net/base/host_resolver_unittest.cc) | 236 | ||||
-rw-r--r-- | net/base/host_resolver_proc.cc | 180 | ||||
-rw-r--r-- | net/base/host_resolver_proc.h | 69 | ||||
-rw-r--r-- | net/base/mock_host_resolver.cc | 154 | ||||
-rw-r--r-- | net/base/mock_host_resolver.h | 143 | ||||
-rw-r--r-- | net/base/run_all_unittests.cc | 12 |
11 files changed, 1325 insertions, 878 deletions
diff --git a/net/base/address_list_unittest.cc b/net/base/address_list_unittest.cc index d594c85..d8ab3c1 100644 --- a/net/base/address_list_unittest.cc +++ b/net/base/address_list_unittest.cc @@ -4,15 +4,8 @@ #include "net/base/address_list.h" -#if defined(OS_WIN) -#include <ws2tcpip.h> -#include <wspiapi.h> // Needed for Win2k compat. -#elif defined(OS_POSIX) -#include <netdb.h> -#include <sys/socket.h> -#endif - #include "base/string_util.h" +#include "net/base/host_resolver_proc.h" #include "net/base/net_util.h" #if defined(OS_WIN) #include "net/base/winsock_init.h" @@ -26,17 +19,9 @@ void CreateAddressList(net::AddressList* addrlist, int port) { #if defined(OS_WIN) net::EnsureWinsockInit(); #endif - std::string portstr = IntToString(port); - - struct addrinfo* result = NULL; - struct addrinfo hints = {0}; - hints.ai_family = AF_UNSPEC; - hints.ai_flags = AI_NUMERICHOST; - hints.ai_socktype = SOCK_STREAM; - - int err = getaddrinfo("192.168.1.1", portstr.c_str(), &hints, &result); - EXPECT_EQ(0, err); - addrlist->Adopt(result); + int rv = SystemHostResolverProc("192.168.1.1", addrlist); + EXPECT_EQ(0, rv); + addrlist->SetPort(port); } TEST(AddressListTest, GetPort) { diff --git a/net/base/host_resolver.cc b/net/base/host_resolver.cc index 4f0ede5..1d17296 100644 --- a/net/base/host_resolver.cc +++ b/net/base/host_resolver.cc @@ -4,616 +4,12 @@ #include "net/base/host_resolver.h" -#if defined(OS_WIN) -#include <ws2tcpip.h> -#include <wspiapi.h> // Needed for Win2k compat. -#elif defined(OS_POSIX) -#include <netdb.h> -#include <sys/socket.h> -#endif -#if defined(OS_LINUX) -#include <resolv.h> -#endif - #include "base/compiler_specific.h" -#include "base/message_loop.h" -#include "base/stl_util-inl.h" -#include "base/string_util.h" -#include "base/time.h" -#include "base/worker_pool.h" -#include "net/base/address_list.h" +#include "base/logging.h" #include "net/base/net_errors.h" -#if defined(OS_LINUX) -#include "base/singleton.h" -#include "base/thread_local_storage.h" -#endif - -#if defined(OS_WIN) -#include "net/base/winsock_init.h" -#endif - namespace net { -//----------------------------------------------------------------------------- - -static HostMapper* host_mapper; - -std::string HostMapper::MapUsingPrevious(const std::string& host) { - return previous_mapper_.get() ? previous_mapper_->Map(host) : host; -} - -HostMapper* SetHostMapper(HostMapper* value) { - std::swap(host_mapper, value); - return value; -} - -#if defined(OS_LINUX) -// On Linux changes to /etc/resolv.conf can go unnoticed thus resulting in -// DNS queries failing either because nameservers are unknown on startup -// or because nameserver info has changed as a result of e.g. connecting to -// a new network. Some distributions patch glibc to stat /etc/resolv.conf -// to try to automatically detect such changes but these patches are not -// universal and even patched systems such as Jaunty appear to need calls -// to res_ninit to reload the nameserver information in different threads. -// -// We adopt the Mozilla solution here which is to call res_ninit when -// lookups fail and to rate limit the reloading to once per second per -// thread. - -// Keep a timer per calling thread to rate limit the calling of res_ninit. -class DnsReloadTimer { - public: - DnsReloadTimer() { - tls_index_.Initialize(SlotReturnFunction); - } - - ~DnsReloadTimer() { } - - // Check if the timer for the calling thread has expired. When no - // timer exists for the calling thread, create one. - bool Expired() { - const base::TimeDelta kRetryTime = base::TimeDelta::FromSeconds(1); - base::TimeTicks now = base::TimeTicks::Now(); - base::TimeTicks* timer_ptr = - static_cast<base::TimeTicks*>(tls_index_.Get()); - - if (!timer_ptr) { - timer_ptr = new base::TimeTicks(); - *timer_ptr = base::TimeTicks::Now(); - tls_index_.Set(timer_ptr); - // Return true to reload dns info on the first call for each thread. - return true; - } else if (now - *timer_ptr > kRetryTime) { - *timer_ptr = now; - return true; - } else { - return false; - } - } - - // Free the allocated timer. - static void SlotReturnFunction(void* data) { - base::TimeTicks* tls_data = static_cast<base::TimeTicks*>(data); - delete tls_data; - } - - private: - // We use thread local storage to identify which base::TimeTicks to - // interact with. - static ThreadLocalStorage::Slot tls_index_ ; - - DISALLOW_COPY_AND_ASSIGN(DnsReloadTimer); -}; - -// A TLS slot to the TimeTicks for the current thread. -// static -ThreadLocalStorage::Slot DnsReloadTimer::tls_index_(base::LINKER_INITIALIZED); - -#endif // defined(OS_LINUX) - -static int HostResolverProc(const std::string& host, struct addrinfo** out) { - struct addrinfo hints = {0}; - 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. - hints.ai_flags = 0; -#else - hints.ai_flags = AI_ADDRCONFIG; -#endif - - // Restrict result set to only this socket type to avoid duplicates. - hints.ai_socktype = SOCK_STREAM; - - int err = getaddrinfo(host.c_str(), NULL, &hints, out); -#if defined(OS_LINUX) - net::DnsReloadTimer* dns_timer = Singleton<net::DnsReloadTimer>::get(); - // If we fail, re-initialise the resolver just in case there have been any - // changes to /etc/resolv.conf and retry. See http://crbug.com/11380 for info. - if (err && dns_timer->Expired()) { - res_nclose(&_res); - if (!res_ninit(&_res)) - err = getaddrinfo(host.c_str(), NULL, &hints, out); - } -#endif - - return err ? ERR_NAME_NOT_RESOLVED : OK; -} - -static int ResolveAddrInfo(HostMapper* mapper, const std::string& host, - struct addrinfo** out) { - if (mapper) { - std::string mapped_host = mapper->Map(host); - - if (mapped_host.empty()) - return ERR_NAME_NOT_RESOLVED; - - return HostResolverProc(mapped_host, out); - } else { - return HostResolverProc(host, out); - } -} - -//----------------------------------------------------------------------------- - -class HostResolver::Request { - public: - Request(int id, const RequestInfo& info, CompletionCallback* callback, - AddressList* addresses) - : id_(id), info_(info), job_(NULL), callback_(callback), - addresses_(addresses) {} - - // Mark the request as cancelled. - void MarkAsCancelled() { - job_ = NULL; - callback_ = NULL; - addresses_ = NULL; - } - - bool was_cancelled() const { - return callback_ == NULL; - } - - void set_job(Job* job) { - DCHECK(job != NULL); - // Identify which job the request is waiting on. - job_ = job; - } - - void OnComplete(int error, const AddressList& addrlist) { - if (error == OK) - addresses_->SetFrom(addrlist, port()); - callback_->Run(error); - } - - int port() const { - return info_.port(); - } - - Job* job() const { - return job_; - } - - int id() const { - return id_; - } - - const RequestInfo& info() const { - return info_; - } - - private: - // Unique ID for this request. Used by observers to identify requests. - int id_; - - // The request info that started the request. - RequestInfo info_; - - // The resolve job (running in worker pool) 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_; - - DISALLOW_COPY_AND_ASSIGN(Request); -}; - -//----------------------------------------------------------------------------- - -// This class represents a request to the worker pool for a "getaddrinfo()" -// call. -class HostResolver::Job : public base::RefCountedThreadSafe<HostResolver::Job> { - public: - Job(HostResolver* resolver, const std::string& host) - : host_(host), - resolver_(resolver), - origin_loop_(MessageLoop::current()), - host_mapper_(host_mapper), - error_(OK), - results_(NULL) { - } - - ~Job() { - if (results_) - freeaddrinfo(results_); - - // Free the requests attached to this job. - STLDeleteElements(&requests_); - } - - // Attaches a request to this job. The job takes ownership of |req| and will - // take care to delete it. - void AddRequest(HostResolver::Request* req) { - req->set_job(this); - requests_.push_back(req); - } - - // Called from origin loop. - void Start() { - // Dispatch the job to a worker thread. - if (!WorkerPool::PostTask(FROM_HERE, - NewRunnableMethod(this, &Job::DoLookup), 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). - error_ = ERR_UNEXPECTED; - MessageLoop::current()->PostTask( - FROM_HERE, NewRunnableMethod(this, &Job::OnLookupComplete)); - } - } - - // Cancels the current job. Callable from origin thread. - void Cancel() { - HostResolver* resolver = resolver_; - resolver_ = NULL; - - // Mark the job as cancelled, so when worker thread completes it will - // not try to post completion to origin loop. - { - AutoLock locked(origin_loop_lock_); - origin_loop_ = NULL; - } - - // We don't have to do anything further to actually cancel the requests - // that were attached to this job (since they are unreachable now). - // But we will call HostResolver::CancelRequest(Request*) on each one - // in order to notify any observers. - for (RequestsList::const_iterator it = requests_.begin(); - it != requests_.end(); ++it) { - HostResolver::Request* req = *it; - if (!req->was_cancelled()) - resolver->CancelRequest(req); - } - } - - // Called from origin thread. - bool was_cancelled() const { - return resolver_ == NULL; - } - - // Called from origin thread. - const std::string& host() const { - return host_; - } - - // Called from origin thread. - const RequestsList& requests() const { - return requests_; - } - - private: - void DoLookup() { - // Running on the worker thread - error_ = ResolveAddrInfo(host_mapper_, host_, &results_); - - Task* reply = NewRunnableMethod(this, &Job::OnLookupComplete); - - // The origin loop could go away while we are trying to post to it, so we - // need to call its PostTask method inside a lock. See ~HostResolver. - { - AutoLock locked(origin_loop_lock_); - if (origin_loop_) { - origin_loop_->PostTask(FROM_HERE, reply); - reply = NULL; - } - } - - // Does nothing if it got posted. - delete reply; - } - - // Callback for when DoLookup() completes (runs on origin thread). - void OnLookupComplete() { - // Should be running on origin loop. - // TODO(eroman): this is being hit by URLRequestTest.CancelTest*, - // because MessageLoop::current() == NULL. - //DCHECK_EQ(origin_loop_, MessageLoop::current()); - DCHECK(error_ || results_); - - if (was_cancelled()) - return; - - DCHECK(!requests_.empty()); - - // Adopt the address list using the port number of the first request. - AddressList addrlist; - if (error_ == OK) { - addrlist.Adopt(results_); - addrlist.SetPort(requests_[0]->port()); - results_ = NULL; - } - - resolver_->OnJobComplete(this, error_, addrlist); - } - - // Set on the origin thread, read on the worker thread. - std::string host_; - - // Only used on the origin thread (where Resolve was called). - HostResolver* resolver_; - RequestsList requests_; // The requests waiting on this job. - - // Used to post ourselves onto the origin thread. - Lock origin_loop_lock_; - MessageLoop* origin_loop_; - - // Hold an owning reference to the host mapper that we are going to use. - // This may not be the current host mapper 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. - scoped_refptr<HostMapper> host_mapper_; - - // Assigned on the worker thread, read on the origin thread. - int error_; - struct addrinfo* results_; - - DISALLOW_COPY_AND_ASSIGN(Job); -}; - -//----------------------------------------------------------------------------- - -HostResolver::HostResolver(int max_cache_entries, int cache_duration_ms) - : cache_(max_cache_entries, cache_duration_ms), next_request_id_(0), - shutdown_(false) { -#if defined(OS_WIN) - EnsureWinsockInit(); -#endif -} - -HostResolver::~HostResolver() { - // Cancel the outstanding jobs. Those jobs may contain several attached - // requests, which will also be cancelled. - for (JobMap::iterator it = jobs_.begin(); it != jobs_.end(); ++it) - it->second->Cancel(); - - // In case we are being deleted during the processing of a callback. - if (cur_completing_job_) - cur_completing_job_->Cancel(); -} - -// TODO(eroman): Don't create cache entries for hostnames which are simply IP -// address literals. -int HostResolver::Resolve(const RequestInfo& info, - AddressList* addresses, - CompletionCallback* callback, - Request** out_req) { - if (shutdown_) - return ERR_UNEXPECTED; - - // Choose a unique ID number for observers to see. - int request_id = next_request_id_++; - - // Notify registered observers. - NotifyObserversStartRequest(request_id, info); - - // If we have an unexpired cache entry, use it. - if (info.allow_cached_response()) { - const HostCache::Entry* cache_entry = cache_.Lookup( - info.hostname(), base::TimeTicks::Now()); - if (cache_entry) { - addresses->SetFrom(cache_entry->addrlist, info.port()); - int error = OK; - - // Notify registered observers. - NotifyObserversFinishRequest(request_id, info, error); - - return error; - } - } - - // If no callback was specified, do a synchronous resolution. - if (!callback) { - struct addrinfo* results; - int error = ResolveAddrInfo(host_mapper, info.hostname(), &results); - - // Adopt the address list. - AddressList addrlist; - if (error == OK) { - addrlist.Adopt(results); - addrlist.SetPort(info.port()); - *addresses = addrlist; - } - - // Write to cache. - cache_.Set(info.hostname(), error, addrlist, base::TimeTicks::Now()); - - // Notify registered observers. - NotifyObserversFinishRequest(request_id, info, error); - - return error; - } - - // Create a handle for this request, and pass it back to the user if they - // asked for it (out_req != NULL). - Request* req = new Request(request_id, info, callback, addresses); - if (out_req) - *out_req = req; - - // Next we need to attach our request to a "job". This job is responsible for - // calling "getaddrinfo(hostname)" on a worker thread. - scoped_refptr<Job> job; - - // If there is already an outstanding job to resolve |info.hostname()|, use - // it. This prevents starting concurrent resolves for the same hostname. - job = FindOutstandingJob(info.hostname()); - if (job) { - job->AddRequest(req); - } else { - // Create a new job for this request. - job = new Job(this, info.hostname()); - job->AddRequest(req); - AddOutstandingJob(job); - // TODO(eroman): Bound the total number of concurrent jobs. - // http://crbug.com/9598 - job->Start(); - } - - // Completion happens during OnJobComplete(Job*). - return ERR_IO_PENDING; -} - -// See OnJobComplete(Job*) for why it is important not to clean out -// cancelled requests from Job::requests_. -void HostResolver::CancelRequest(Request* req) { - DCHECK(req); - DCHECK(req->job()); - // NULL out the fields of req, to mark it as cancelled. - req->MarkAsCancelled(); - NotifyObserversCancelRequest(req->id(), req->info()); -} - -void HostResolver::AddObserver(Observer* observer) { - observers_.push_back(observer); -} - -void HostResolver::RemoveObserver(Observer* observer) { - ObserversList::iterator it = - std::find(observers_.begin(), observers_.end(), observer); - - // Observer must exist. - DCHECK(it != observers_.end()); - - observers_.erase(it); -} - -void HostResolver::Shutdown() { - shutdown_ = true; - - // Cancel the outstanding jobs. - for (JobMap::iterator it = jobs_.begin(); it != jobs_.end(); ++it) - it->second->Cancel(); - jobs_.clear(); -} - -void HostResolver::AddOutstandingJob(Job* job) { - scoped_refptr<Job>& found_job = jobs_[job->host()]; - DCHECK(!found_job); - found_job = job; -} - -HostResolver::Job* HostResolver::FindOutstandingJob( - const std::string& hostname) { - JobMap::iterator it = jobs_.find(hostname); - if (it != jobs_.end()) - return it->second; - return NULL; -} - -void HostResolver::RemoveOutstandingJob(Job* job) { - JobMap::iterator it = jobs_.find(job->host()); - DCHECK(it != jobs_.end()); - DCHECK_EQ(it->second.get(), job); - jobs_.erase(it); -} - -void HostResolver::OnJobComplete(Job* job, - int error, - const AddressList& addrlist) { - RemoveOutstandingJob(job); - - // Write result to the cache. - cache_.Set(job->host(), error, addrlist, base::TimeTicks::Now()); - - // Make a note that we are executing within OnJobComplete() in case the - // HostResolver is deleted by a callback invocation. - DCHECK(!cur_completing_job_); - cur_completing_job_ = job; - - // Complete all of the requests that were attached to the job. - for (RequestsList::const_iterator it = job->requests().begin(); - it != job->requests().end(); ++it) { - Request* req = *it; - if (!req->was_cancelled()) { - DCHECK_EQ(job, req->job()); - - // Notify registered observers. - NotifyObserversFinishRequest(req->id(), req->info(), error); - - req->OnComplete(error, addrlist); - - // Check if the job was cancelled as a result of running the callback. - // (Meaning that |this| was deleted). - if (job->was_cancelled()) - return; - } - } - - cur_completing_job_ = NULL; -} - -void HostResolver::NotifyObserversStartRequest(int request_id, - const RequestInfo& info) { - for (ObserversList::iterator it = observers_.begin(); - it != observers_.end(); ++it) { - (*it)->OnStartResolution(request_id, info); - } -} - -void HostResolver::NotifyObserversFinishRequest(int request_id, - const RequestInfo& info, - int error) { - bool was_resolved = error == OK; - for (ObserversList::iterator it = observers_.begin(); - it != observers_.end(); ++it) { - (*it)->OnFinishResolutionWithStatus(request_id, was_resolved, info); - } -} - -void HostResolver::NotifyObserversCancelRequest(int request_id, - const RequestInfo& info) { - for (ObserversList::iterator it = observers_.begin(); - it != observers_.end(); ++it) { - (*it)->OnCancelResolution(request_id, info); - } -} - -//----------------------------------------------------------------------------- - SingleRequestHostResolver::SingleRequestHostResolver(HostResolver* resolver) : resolver_(resolver), cur_request_(NULL), @@ -634,7 +30,7 @@ int SingleRequestHostResolver::Resolve(const HostResolver::RequestInfo& info, CompletionCallback* callback) { DCHECK(!cur_request_ && !cur_request_callback_) << "resolver already in use"; - HostResolver::Request* request = NULL; + HostResolver::RequestHandle request = NULL; // We need to be notified of completion before |callback| is called, so that // we can clear out |cur_request_*|. diff --git a/net/base/host_resolver.h b/net/base/host_resolver.h index 5a6c548..c1ac13f 100644 --- a/net/base/host_resolver.h +++ b/net/base/host_resolver.h @@ -6,57 +6,26 @@ #define NET_BASE_HOST_RESOLVER_H_ #include <string> -#include <vector> -#include "base/basictypes.h" -#include "base/lock.h" #include "base/ref_counted.h" #include "googleurl/src/gurl.h" #include "net/base/completion_callback.h" -#include "net/base/host_cache.h" class MessageLoop; namespace net { class AddressList; -class HostMapper; // This class represents the task of resolving hostnames (or IP address // literal) to an AddressList object. // -// HostResolver handles multiple requests at a time, so when cancelling a -// request the Request* handle that was returned by Resolve() needs to be +// 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). -// -// For each hostname that is requested, HostResolver creates a -// HostResolver::Job. This job gets dispatched to a thread in the global -// WorkerPool, where it runs "getaddrinfo(hostname)". If requests for that same -// host are made while the job is already outstanding, then 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: -// -// -// +------------- HostResolver ---------------+ -// | | | -// Job Job Job -// (for host1) (for host2) (for hostX) -// / | | / | | / | | -// Request ... Request Request ... Request Request ... Request -// (port1) (port2) (port3) (port4) (port5) (portX) -// -// -// When a HostResolver::Job finishes its work in the threadpool, 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! -// class HostResolver : public base::RefCounted<HostResolver> { public: // The parameters for doing a Resolve(). |hostname| and |port| are required, @@ -120,20 +89,15 @@ class HostResolver : public base::RefCounted<HostResolver> { virtual void OnCancelResolution(int id, const RequestInfo& info) = 0; }; - // Creates a HostResolver that caches up to |max_cache_entries| for - // |cache_duration_ms| milliseconds. - // - // TODO(eroman): Get rid of the default parameters as it violate google - // style. This is temporary to help with refactoring. - HostResolver(int max_cache_entries = 100, int cache_duration_ms = 60000); + // Opaque type used to cancel a request. + typedef void* RequestHandle; + + HostResolver() {} // If any completion callbacks are pending when the resolver is destroyed, // the host resolutions are cancelled, and the completion callbacks will not // be called. - ~HostResolver(); - - // Opaque type used to cancel a request. - class Request; + 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 @@ -147,74 +111,23 @@ class HostResolver : public base::RefCounted<HostResolver> { // result code will be passed to the completion callback. If |req| is // non-NULL, then |*req| will be filled with a handle to the async request. // This handle is not valid after the request has completed. - int Resolve(const RequestInfo& info, AddressList* addresses, - CompletionCallback* callback, Request** req); + virtual int Resolve(const RequestInfo& info, AddressList* addresses, + CompletionCallback* callback, RequestHandle* out_req) = 0; // Cancels the specified request. |req| is the handle returned by Resolve(). // After a request is cancelled, its completion callback will not be called. - void CancelRequest(Request* req); + virtual void CancelRequest(RequestHandle req) = 0; // Adds an observer to this resolver. The observer will be notified of the // start and completion of all requests (excluding cancellation). |observer| // must remain valid for the duration of this HostResolver's lifetime. - void AddObserver(Observer* observer); + virtual void AddObserver(Observer* observer) = 0; // Unregisters an observer previously added by AddObserver(). - void RemoveObserver(Observer* observer); - - // TODO(eroman): temp hack for http://crbug.com/15513 - void Shutdown(); - - private: - class Job; - typedef std::vector<Request*> RequestsList; - typedef base::hash_map<std::string, scoped_refptr<Job> > JobMap; - typedef std::vector<Observer*> ObserversList; - - // Adds a job to outstanding jobs list. - void AddOutstandingJob(Job* job); - - // Returns the outstanding job for |hostname|, or NULL if there is none. - Job* FindOutstandingJob(const std::string& hostname); - - // Removes |job| from the outstanding jobs list. - void RemoveOutstandingJob(Job* job); - - // Callback for when |job| has completed with |error| and |addrlist|. - void OnJobComplete(Job* job, int error, const AddressList& addrlist); - - // Notify all observers of the start of a resolve request. - void NotifyObserversStartRequest(int request_id, - const RequestInfo& info); - - // Notify all observers of the completion of a resolve request. - void NotifyObserversFinishRequest(int request_id, - const RequestInfo& info, - int error); - - // Notify all observers of the cancellation of a resolve request. - void NotifyObserversCancelRequest(int request_id, - const RequestInfo& info); - - // Cache of host resolution results. - HostCache cache_; - - // Map from hostname to outstanding job. - JobMap jobs_; - - // The job that OnJobComplete() is currently processing (needed in case - // HostResolver gets deleted from within the callback). - scoped_refptr<Job> cur_completing_job_; - - // The observers to notify when a request starts/ends. - ObserversList observers_; - - // Monotonically increasing ID number to assign to the next request. - // Observers are the only consumers of this ID number. - int next_request_id_; + virtual void RemoveObserver(Observer* observer) = 0; // TODO(eroman): temp hack for http://crbug.com/15513 - bool shutdown_; + virtual void Shutdown() = 0; DISALLOW_COPY_AND_ASSIGN(HostResolver); }; @@ -245,7 +158,7 @@ class SingleRequestHostResolver { scoped_refptr<HostResolver> resolver_; // The current request (if any). - HostResolver::Request* cur_request_; + HostResolver::RequestHandle cur_request_; CompletionCallback* cur_request_callback_; // Completion callback for when request to |resolver_| completes. @@ -254,46 +167,10 @@ class SingleRequestHostResolver { DISALLOW_COPY_AND_ASSIGN(SingleRequestHostResolver); }; -// A helper class used in unit tests to alter hostname mappings. See -// SetHostMapper for details. -class HostMapper : public base::RefCountedThreadSafe<HostMapper> { - public: - virtual ~HostMapper() {} - - // Returns possibly altered hostname, or empty string to simulate - // a failed lookup. - virtual std::string Map(const std::string& host) = 0; - - protected: - // Ask previous host mapper (if set) for mapping of given host. - std::string MapUsingPrevious(const std::string& host); - - private: - friend class ScopedHostMapper; - - // Set mapper to ask when this mapper doesn't want to modify the result. - void set_previous_mapper(HostMapper* mapper) { - previous_mapper_ = mapper; - } - - scoped_refptr<HostMapper> previous_mapper_; -}; - -#ifdef UNIT_TEST -// This function is designed to allow unit tests to override the behavior of -// HostResolver. For example, a HostMapper instance can force all hostnames -// to map to a fixed IP address such as 127.0.0.1. -// -// The previously set HostMapper (or NULL if there was none) is returned. -// -// NOTE: This function is not thread-safe, so take care to only call this -// function while there are no outstanding HostResolver instances. -// -// NOTE: In most cases, you should use ScopedHostMapper instead, which is -// defined in host_resolver_unittest.h -// -HostMapper* SetHostMapper(HostMapper* host_mapper); -#endif +// 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). +HostResolver* CreateSystemHostResolver(); } // namespace net diff --git a/net/base/host_resolver_impl.cc b/net/base/host_resolver_impl.cc new file mode 100644 index 0000000..2a0eb0f --- /dev/null +++ b/net/base/host_resolver_impl.cc @@ -0,0 +1,483 @@ +// Copyright (c) 2006-2008 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/base/host_resolver_impl.h" + +#if defined(OS_WIN) +#include <ws2tcpip.h> +#include <wspiapi.h> // Needed for Win2k compat. +#elif defined(OS_POSIX) +#include <netdb.h> +#include <sys/socket.h> +#endif +#if defined(OS_LINUX) +#include <resolv.h> +#endif + +#include "base/compiler_specific.h" +#include "base/message_loop.h" +#include "base/stl_util-inl.h" +#include "base/string_util.h" +#include "base/time.h" +#include "base/worker_pool.h" +#include "net/base/address_list.h" +#include "net/base/host_resolver_proc.h" +#include "net/base/net_errors.h" + +#if defined(OS_WIN) +#include "net/base/winsock_init.h" +#endif + +namespace net { + +HostResolver* CreateSystemHostResolver() { + static const size_t kMaxHostCacheEntries = 100; + static const size_t kHostCacheExpirationMs = 60000; // 1 minute. + return new HostResolverImpl( + NULL, kMaxHostCacheEntries, kHostCacheExpirationMs); +} + +static int ResolveAddrInfo(HostResolverProc* resolver_proc, + const std::string& host, AddressList* out) { + if (resolver_proc) { + // Use the custom procedure. + return resolver_proc->Resolve(host, out); + } else { + // Use the system procedure (getaddrinfo). + return SystemHostResolverProc(host, out); + } +} + +//----------------------------------------------------------------------------- + +class HostResolverImpl::Request { + public: + Request(int id, const RequestInfo& info, CompletionCallback* callback, + AddressList* addresses) + : id_(id), info_(info), job_(NULL), callback_(callback), + addresses_(addresses) {} + + // Mark the request as cancelled. + void MarkAsCancelled() { + job_ = NULL; + callback_ = NULL; + addresses_ = NULL; + } + + bool was_cancelled() const { + return callback_ == NULL; + } + + void set_job(Job* job) { + DCHECK(job != NULL); + // Identify which job the request is waiting on. + job_ = job; + } + + void OnComplete(int error, const AddressList& addrlist) { + if (error == OK) + addresses_->SetFrom(addrlist, port()); + callback_->Run(error); + } + + int port() const { + return info_.port(); + } + + Job* job() const { + return job_; + } + + int id() const { + return id_; + } + + const RequestInfo& info() const { + return info_; + } + + private: + // Unique ID for this request. Used by observers to identify requests. + int id_; + + // The request info that started the request. + RequestInfo info_; + + // The resolve job (running in worker pool) 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_; + + DISALLOW_COPY_AND_ASSIGN(Request); +}; + +//----------------------------------------------------------------------------- + +// This class represents a request to the worker pool for a "getaddrinfo()" +// call. +class HostResolverImpl::Job + : public base::RefCountedThreadSafe<HostResolverImpl::Job> { + public: + Job(HostResolverImpl* resolver, const std::string& host) + : host_(host), + resolver_(resolver), + origin_loop_(MessageLoop::current()), + resolver_proc_(resolver->effective_resolver_proc()), + error_(OK) { + } + + ~Job() { + // Free the requests attached to this job. + STLDeleteElements(&requests_); + } + + // Attaches a request to this job. The job takes ownership of |req| and will + // take care to delete it. + void AddRequest(Request* req) { + req->set_job(this); + requests_.push_back(req); + } + + // Called from origin loop. + void Start() { + // Dispatch the job to a worker thread. + if (!WorkerPool::PostTask(FROM_HERE, + NewRunnableMethod(this, &Job::DoLookup), 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). + error_ = ERR_UNEXPECTED; + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableMethod(this, &Job::OnLookupComplete)); + } + } + + // Cancels the current job. Callable from origin thread. + void Cancel() { + HostResolver* resolver = resolver_; + resolver_ = NULL; + + // Mark the job as cancelled, so when worker thread completes it will + // not try to post completion to origin loop. + { + AutoLock locked(origin_loop_lock_); + origin_loop_ = NULL; + } + + // We don't have to do anything further to actually cancel the requests + // that were attached to this job (since they are unreachable now). + // But we will call HostResolverImpl::CancelRequest(Request*) on each one + // in order to notify any observers. + for (RequestsList::const_iterator it = requests_.begin(); + it != requests_.end(); ++it) { + HostResolverImpl::Request* req = *it; + if (!req->was_cancelled()) + resolver->CancelRequest(req); + } + } + + // Called from origin thread. + bool was_cancelled() const { + return resolver_ == NULL; + } + + // Called from origin thread. + const std::string& host() const { + return host_; + } + + // Called from origin thread. + const RequestsList& requests() const { + return requests_; + } + + private: + void DoLookup() { + // Running on the worker thread + error_ = ResolveAddrInfo(resolver_proc_, host_, &results_); + + Task* reply = NewRunnableMethod(this, &Job::OnLookupComplete); + + // The origin loop could go away while we are trying to post to it, so we + // need to call its PostTask method inside a lock. See ~HostResolver. + { + AutoLock locked(origin_loop_lock_); + if (origin_loop_) { + origin_loop_->PostTask(FROM_HERE, reply); + reply = NULL; + } + } + + // Does nothing if it got posted. + delete reply; + } + + // Callback for when DoLookup() completes (runs on origin thread). + void OnLookupComplete() { + // Should be running on origin loop. + // TODO(eroman): this is being hit by URLRequestTest.CancelTest*, + // because MessageLoop::current() == NULL. + //DCHECK_EQ(origin_loop_, MessageLoop::current()); + DCHECK(error_ || results_.head()); + + if (was_cancelled()) + return; + + DCHECK(!requests_.empty()); + + // Use the port number of the first request. + if (error_ == OK) + results_.SetPort(requests_[0]->port()); + + resolver_->OnJobComplete(this, error_, results_); + } + + // Set on the origin thread, read on the worker thread. + std::string host_; + + // Only used on the origin thread (where Resolve was called). + HostResolverImpl* resolver_; + RequestsList requests_; // The requests waiting on this job. + + // Used to post ourselves onto the origin thread. + Lock origin_loop_lock_; + MessageLoop* origin_loop_; + + // Hold 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. + scoped_refptr<HostResolverProc> resolver_proc_; + + // Assigned on the worker thread, read on the origin thread. + int error_; + AddressList results_; + + DISALLOW_COPY_AND_ASSIGN(Job); +}; + +//----------------------------------------------------------------------------- + +HostResolverImpl::HostResolverImpl(HostResolverProc* resolver_proc, + int max_cache_entries, + int cache_duration_ms) + : cache_(max_cache_entries, cache_duration_ms), next_request_id_(0), + resolver_proc_(resolver_proc), shutdown_(false) { +#if defined(OS_WIN) + EnsureWinsockInit(); +#endif +} + +HostResolverImpl::~HostResolverImpl() { + // Cancel the outstanding jobs. Those jobs may contain several attached + // requests, which will also be cancelled. + for (JobMap::iterator it = jobs_.begin(); it != jobs_.end(); ++it) + it->second->Cancel(); + + // In case we are being deleted during the processing of a callback. + if (cur_completing_job_) + cur_completing_job_->Cancel(); +} + +// TODO(eroman): Don't create cache entries for hostnames which are simply IP +// address literals. +int HostResolverImpl::Resolve(const RequestInfo& info, + AddressList* addresses, + CompletionCallback* callback, + RequestHandle* out_req) { + if (shutdown_) + return ERR_UNEXPECTED; + + // Choose a unique ID number for observers to see. + int request_id = next_request_id_++; + + // Notify registered observers. + NotifyObserversStartRequest(request_id, info); + + // If we have an unexpired cache entry, use it. + if (info.allow_cached_response()) { + const HostCache::Entry* cache_entry = cache_.Lookup( + info.hostname(), base::TimeTicks::Now()); + if (cache_entry) { + addresses->SetFrom(cache_entry->addrlist, info.port()); + int error = cache_entry->error; + + // Notify registered observers. + NotifyObserversFinishRequest(request_id, info, error); + + return error; + } + } + + // If no callback was specified, do a synchronous resolution. + if (!callback) { + AddressList addrlist; + int error = ResolveAddrInfo( + effective_resolver_proc(), info.hostname(), &addrlist); + if (error == OK) { + addrlist.SetPort(info.port()); + *addresses = addrlist; + } + + // Write to cache. + cache_.Set(info.hostname(), error, addrlist, base::TimeTicks::Now()); + + // Notify registered observers. + NotifyObserversFinishRequest(request_id, info, error); + + return error; + } + + // Create a handle for this request, and pass it back to the user if they + // asked for it (out_req != NULL). + Request* req = new Request(request_id, info, callback, addresses); + if (out_req) + *out_req = reinterpret_cast<RequestHandle>(req); + + // Next we need to attach our request to a "job". This job is responsible for + // calling "getaddrinfo(hostname)" on a worker thread. + scoped_refptr<Job> job; + + // If there is already an outstanding job to resolve |info.hostname()|, use + // it. This prevents starting concurrent resolves for the same hostname. + job = FindOutstandingJob(info.hostname()); + if (job) { + job->AddRequest(req); + } else { + // Create a new job for this request. + job = new Job(this, info.hostname()); + job->AddRequest(req); + AddOutstandingJob(job); + // TODO(eroman): Bound the total number of concurrent jobs. + // http://crbug.com/9598 + job->Start(); + } + + // Completion happens during OnJobComplete(Job*). + return ERR_IO_PENDING; +} + +// See OnJobComplete(Job*) for why it is important not to clean out +// cancelled requests from Job::requests_. +void HostResolverImpl::CancelRequest(RequestHandle req_handle) { + Request* req = reinterpret_cast<Request*>(req_handle); + DCHECK(req); + DCHECK(req->job()); + // NULL out the fields of req, to mark it as cancelled. + req->MarkAsCancelled(); + NotifyObserversCancelRequest(req->id(), req->info()); +} + +void HostResolverImpl::AddObserver(Observer* observer) { + observers_.push_back(observer); +} + +void HostResolverImpl::RemoveObserver(Observer* observer) { + ObserversList::iterator it = + std::find(observers_.begin(), observers_.end(), observer); + + // Observer must exist. + DCHECK(it != observers_.end()); + + observers_.erase(it); +} + +void HostResolverImpl::Shutdown() { + shutdown_ = true; + + // Cancel the outstanding jobs. + for (JobMap::iterator it = jobs_.begin(); it != jobs_.end(); ++it) + it->second->Cancel(); + jobs_.clear(); +} + +void HostResolverImpl::AddOutstandingJob(Job* job) { + scoped_refptr<Job>& found_job = jobs_[job->host()]; + DCHECK(!found_job); + found_job = job; +} + +HostResolverImpl::Job* HostResolverImpl::FindOutstandingJob( + const std::string& hostname) { + JobMap::iterator it = jobs_.find(hostname); + if (it != jobs_.end()) + return it->second; + return NULL; +} + +void HostResolverImpl::RemoveOutstandingJob(Job* job) { + JobMap::iterator it = jobs_.find(job->host()); + DCHECK(it != jobs_.end()); + DCHECK_EQ(it->second.get(), job); + jobs_.erase(it); +} + +void HostResolverImpl::OnJobComplete(Job* job, + int error, + const AddressList& addrlist) { + RemoveOutstandingJob(job); + + // Write result to the cache. + cache_.Set(job->host(), error, addrlist, base::TimeTicks::Now()); + + // Make a note that we are executing within OnJobComplete() in case the + // HostResolver is deleted by a callback invocation. + DCHECK(!cur_completing_job_); + cur_completing_job_ = job; + + // Complete all of the requests that were attached to the job. + for (RequestsList::const_iterator it = job->requests().begin(); + it != job->requests().end(); ++it) { + Request* req = *it; + if (!req->was_cancelled()) { + DCHECK_EQ(job, req->job()); + + // Notify registered observers. + NotifyObserversFinishRequest(req->id(), req->info(), error); + + req->OnComplete(error, addrlist); + + // Check if the job was cancelled as a result of running the callback. + // (Meaning that |this| was deleted). + if (job->was_cancelled()) + return; + } + } + + cur_completing_job_ = NULL; +} + +void HostResolverImpl::NotifyObserversStartRequest(int request_id, + const RequestInfo& info) { + for (ObserversList::iterator it = observers_.begin(); + it != observers_.end(); ++it) { + (*it)->OnStartResolution(request_id, info); + } +} + +void HostResolverImpl::NotifyObserversFinishRequest(int request_id, + const RequestInfo& info, + int error) { + bool was_resolved = error == OK; + for (ObserversList::iterator it = observers_.begin(); + it != observers_.end(); ++it) { + (*it)->OnFinishResolutionWithStatus(request_id, was_resolved, info); + } +} + +void HostResolverImpl::NotifyObserversCancelRequest(int request_id, + const RequestInfo& info) { + for (ObserversList::iterator it = observers_.begin(); + it != observers_.end(); ++it) { + (*it)->OnCancelResolution(request_id, info); + } +} + +} // namespace net diff --git a/net/base/host_resolver_impl.h b/net/base/host_resolver_impl.h new file mode 100644 index 0000000..6ece456 --- /dev/null +++ b/net/base/host_resolver_impl.h @@ -0,0 +1,136 @@ +// Copyright (c) 2006-2008 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_BASE_HOST_RESOLVER_IMPL_H_ +#define NET_BASE_HOST_RESOLVER_IMPL_H_ + +#include <string> +#include <vector> + +#include "net/base/host_cache.h" +#include "net/base/host_resolver.h" +#include "net/base/host_resolver_proc.h" + +namespace net { + +// For each hostname that is requested, HostResolver creates a +// HostResolverImpl::Job. This job gets dispatched to a thread in the global +// WorkerPool, where it runs SystemHostResolverProc(). If requests for that same +// host are made while the job is already outstanding, then 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) (for host2) (for hostX) +// / | | / | | / | | +// Request ... Request Request ... Request Request ... Request +// (port1) (port2) (port3) (port4) (port5) (portX) +// +// +// When a HostResolverImpl::Job finishes its work in the threadpool, 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! +// +class HostResolverImpl : public HostResolver { + public: + // Creates a HostResolver that caches up to |max_cache_entries| for + // |cache_duration_ms| milliseconds. |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) + HostResolverImpl(HostResolverProc* resolver_proc, + int max_cache_entries, + int cache_duration_ms); + + // 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(); + + // HostResolver methods: + virtual int Resolve(const RequestInfo& info, AddressList* addresses, + CompletionCallback* callback, RequestHandle* out_req); + virtual void CancelRequest(RequestHandle req); + virtual void AddObserver(Observer* observer); + virtual void RemoveObserver(Observer* observer); + + // TODO(eroman): temp hack for http://crbug.com/15513 + virtual void Shutdown(); + + private: + class Job; + class Request; + typedef std::vector<Request*> RequestsList; + typedef base::hash_map<std::string, scoped_refptr<Job> > JobMap; + typedef std::vector<Observer*> ObserversList; + + // Returns the HostResolverProc to use for this instance. + HostResolverProc* effective_resolver_proc() const { + return resolver_proc_ ? + resolver_proc_.get() : HostResolverProc::GetDefault(); + } + + // Adds a job to outstanding jobs list. + void AddOutstandingJob(Job* job); + + // Returns the outstanding job for |hostname|, or NULL if there is none. + Job* FindOutstandingJob(const std::string& hostname); + + // Removes |job| from the outstanding jobs list. + void RemoveOutstandingJob(Job* job); + + // Callback for when |job| has completed with |error| and |addrlist|. + void OnJobComplete(Job* job, int error, const AddressList& addrlist); + + // Notify all observers of the start of a resolve request. + void NotifyObserversStartRequest(int request_id, + const RequestInfo& info); + + // Notify all observers of the completion of a resolve request. + void NotifyObserversFinishRequest(int request_id, + const RequestInfo& info, + int error); + + // Notify all observers of the cancellation of a resolve request. + void NotifyObserversCancelRequest(int request_id, + const RequestInfo& info); + + // Cache of host resolution results. + HostCache cache_; + + // Map from hostname to outstanding job. + JobMap jobs_; + + // The job that OnJobComplete() is currently processing (needed in case + // HostResolver gets deleted from within the callback). + scoped_refptr<Job> cur_completing_job_; + + // The observers to notify when a request starts/ends. + ObserversList observers_; + + // Monotonically increasing ID number to assign to the next request. + // Observers are the only consumers of this ID number. + int next_request_id_; + + // 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_; + + // TODO(eroman): temp hack for http://crbug.com/15513 + bool shutdown_; + + DISALLOW_COPY_AND_ASSIGN(HostResolverImpl); +}; + +} // namespace net + +#endif // NET_BASE_HOST_RESOLVER_IMPL_H_ diff --git a/net/base/host_resolver_unittest.cc b/net/base/host_resolver_impl_unittest.cc index 32b6af6..0ff345c 100644 --- a/net/base/host_resolver_unittest.cc +++ b/net/base/host_resolver_impl_unittest.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "net/base/host_resolver.h" +#include "net/base/host_resolver_impl.h" #if defined(OS_WIN) #include <ws2tcpip.h> @@ -18,39 +18,43 @@ #include "base/ref_counted.h" #include "net/base/address_list.h" #include "net/base/completion_callback.h" -#include "net/base/host_resolver_unittest.h" +#include "net/base/mock_host_resolver.h" #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" #include "testing/gtest/include/gtest/gtest.h" -using net::RuleBasedHostMapper; -using net::ScopedHostMapper; -using net::WaitingHostMapper; +using net::HostResolverImpl; +using net::RuleBasedHostResolverProc; +using net::WaitingHostResolverProc; // TODO(eroman): // - Test mixing async with sync (in particular how does sync update the // cache while an async is already pending). namespace { +static const int kMaxCacheEntries = 100; +static const int kMaxCacheAgeMs = 60000; -// A variant of WaitingHostMapper that pushes each host mapped into a list. +// A variant of WaitingHostResolverProc that pushes each host mapped into a +// list. // (and uses a manual-reset event rather than auto-reset). -class CapturingHostMapper : public net::HostMapper { +class CapturingHostResolverProc : public net::HostResolverProc { public: - CapturingHostMapper() : event_(true, false) { + explicit CapturingHostResolverProc(HostResolverProc* previous) + : net::HostResolverProc(previous), event_(true, false) { } void Signal() { event_.Signal(); } - virtual std::string Map(const std::string& host) { + virtual int Resolve(const std::string& host, net::AddressList* addrlist) { event_.Wait(); { AutoLock l(lock_); capture_list_.push_back(host); } - return MapUsingPrevious(host); + return ResolveUsingPrevious(host, addrlist); } std::vector<std::string> GetCaptureList() const { @@ -134,7 +138,7 @@ class ResolveRequest { // The request details. net::HostResolver::RequestInfo info_; - net::HostResolver::Request* req_; + net::HostResolver::RequestHandle req_; // The result of the resolve. int result_; @@ -150,18 +154,18 @@ class ResolveRequest { DISALLOW_COPY_AND_ASSIGN(ResolveRequest); }; -class HostResolverTest : public testing::Test { +class HostResolverImplTest : public testing::Test { public: - HostResolverTest() + HostResolverImplTest() : callback_called_(false), ALLOW_THIS_IN_INITIALIZER_LIST( - callback_(this, &HostResolverTest::OnLookupFinished)) { + callback_(this, &HostResolverImplTest::OnLookupFinished)) { } protected: bool callback_called_; int callback_result_; - net::CompletionCallbackImpl<HostResolverTest> callback_; + net::CompletionCallbackImpl<HostResolverImplTest> callback_; private: void OnLookupFinished(int result) { @@ -171,14 +175,16 @@ class HostResolverTest : public testing::Test { } }; -TEST_F(HostResolverTest, SynchronousLookup) { - scoped_refptr<net::HostResolver> host_resolver(new net::HostResolver); +TEST_F(HostResolverImplTest, SynchronousLookup) { net::AddressList adrlist; const int kPortnum = 80; - scoped_refptr<RuleBasedHostMapper> mapper = new RuleBasedHostMapper(); - mapper->AddRule("just.testing", "192.168.1.42"); - ScopedHostMapper scoped_mapper(mapper.get()); + scoped_refptr<RuleBasedHostResolverProc> resolver_proc = + new RuleBasedHostResolverProc(NULL); + resolver_proc->AddRule("just.testing", "192.168.1.42"); + + scoped_refptr<net::HostResolver> host_resolver( + new HostResolverImpl(resolver_proc, kMaxCacheEntries, kMaxCacheAgeMs)); net::HostResolver::RequestInfo info("just.testing", kPortnum); int err = host_resolver->Resolve(info, &adrlist, NULL, NULL); @@ -194,14 +200,16 @@ TEST_F(HostResolverTest, SynchronousLookup) { EXPECT_TRUE(htonl(0xc0a8012a) == sa_in->sin_addr.s_addr); } -TEST_F(HostResolverTest, AsynchronousLookup) { - scoped_refptr<net::HostResolver> host_resolver(new net::HostResolver); +TEST_F(HostResolverImplTest, AsynchronousLookup) { net::AddressList adrlist; const int kPortnum = 80; - scoped_refptr<RuleBasedHostMapper> mapper = new RuleBasedHostMapper(); - mapper->AddRule("just.testing", "192.168.1.42"); - ScopedHostMapper scoped_mapper(mapper.get()); + scoped_refptr<RuleBasedHostResolverProc> resolver_proc = + new RuleBasedHostResolverProc(NULL); + resolver_proc->AddRule("just.testing", "192.168.1.42"); + + scoped_refptr<net::HostResolver> host_resolver( + new HostResolverImpl(resolver_proc, kMaxCacheEntries, kMaxCacheAgeMs)); net::HostResolver::RequestInfo info("just.testing", kPortnum); int err = host_resolver->Resolve(info, &adrlist, &callback_, NULL); @@ -222,12 +230,13 @@ TEST_F(HostResolverTest, AsynchronousLookup) { EXPECT_TRUE(htonl(0xc0a8012a) == sa_in->sin_addr.s_addr); } -TEST_F(HostResolverTest, CanceledAsynchronousLookup) { - scoped_refptr<WaitingHostMapper> mapper = new WaitingHostMapper(); - ScopedHostMapper scoped_mapper(mapper.get()); +TEST_F(HostResolverImplTest, CanceledAsynchronousLookup) { + scoped_refptr<WaitingHostResolverProc> resolver_proc = + new WaitingHostResolverProc(NULL); { - scoped_refptr<net::HostResolver> host_resolver(new net::HostResolver); + scoped_refptr<net::HostResolver> host_resolver( + new HostResolverImpl(resolver_proc, kMaxCacheEntries, kMaxCacheAgeMs)); net::AddressList adrlist; const int kPortnum = 80; @@ -242,19 +251,20 @@ TEST_F(HostResolverTest, CanceledAsynchronousLookup) { MessageLoop::current()->Run(); } - mapper->Signal(); + resolver_proc->Signal(); EXPECT_FALSE(callback_called_); } -TEST_F(HostResolverTest, NumericIPv4Address) { +TEST_F(HostResolverImplTest, NumericIPv4Address) { // Stevens says dotted quads with AI_UNSPEC resolve to a single sockaddr_in. - scoped_refptr<RuleBasedHostMapper> mapper = new RuleBasedHostMapper(); - mapper->AllowDirectLookup("*"); - ScopedHostMapper scoped_mapper(mapper.get()); + scoped_refptr<RuleBasedHostResolverProc> resolver_proc = + new RuleBasedHostResolverProc(NULL); + resolver_proc->AllowDirectLookup("*"); - scoped_refptr<net::HostResolver> host_resolver(new net::HostResolver); + scoped_refptr<net::HostResolver> host_resolver( + new HostResolverImpl(resolver_proc, kMaxCacheEntries, kMaxCacheAgeMs)); net::AddressList adrlist; const int kPortnum = 5555; net::HostResolver::RequestInfo info("127.1.2.3", kPortnum); @@ -271,14 +281,15 @@ TEST_F(HostResolverTest, NumericIPv4Address) { EXPECT_TRUE(htonl(0x7f010203) == sa_in->sin_addr.s_addr); } -TEST_F(HostResolverTest, NumericIPv6Address) { - scoped_refptr<RuleBasedHostMapper> mapper = new RuleBasedHostMapper(); - mapper->AllowDirectLookup("*"); - ScopedHostMapper scoped_mapper(mapper.get()); +TEST_F(HostResolverImplTest, NumericIPv6Address) { + scoped_refptr<RuleBasedHostResolverProc> resolver_proc = + new RuleBasedHostResolverProc(NULL); + resolver_proc->AllowDirectLookup("*"); // Resolve a plain IPv6 address. Don't worry about [brackets], because // the caller should have removed them. - scoped_refptr<net::HostResolver> host_resolver(new net::HostResolver); + scoped_refptr<net::HostResolver> host_resolver( + new HostResolverImpl(resolver_proc, kMaxCacheEntries, kMaxCacheAgeMs)); net::AddressList adrlist; const int kPortnum = 5555; net::HostResolver::RequestInfo info("2001:db8::1", kPortnum); @@ -307,12 +318,13 @@ TEST_F(HostResolverTest, NumericIPv6Address) { } } -TEST_F(HostResolverTest, EmptyHost) { - scoped_refptr<RuleBasedHostMapper> mapper = new RuleBasedHostMapper(); - mapper->AllowDirectLookup("*"); - ScopedHostMapper scoped_mapper(mapper.get()); +TEST_F(HostResolverImplTest, EmptyHost) { + scoped_refptr<RuleBasedHostResolverProc> resolver_proc = + new RuleBasedHostResolverProc(NULL); + resolver_proc->AllowDirectLookup("*"); - scoped_refptr<net::HostResolver> host_resolver(new net::HostResolver); + scoped_refptr<net::HostResolver> host_resolver( + new HostResolverImpl(resolver_proc, kMaxCacheEntries, kMaxCacheAgeMs)); net::AddressList adrlist; const int kPortnum = 5555; net::HostResolver::RequestInfo info("", kPortnum); @@ -320,13 +332,13 @@ TEST_F(HostResolverTest, EmptyHost) { EXPECT_EQ(net::ERR_NAME_NOT_RESOLVED, err); } -// Helper class used by HostResolverTest.DeDupeRequests. It receives request +// Helper class used by HostResolverImplTest.DeDupeRequests. It receives request // completion notifications for all the resolves, so it can tally up and // determine when we are done. class DeDupeRequestsVerifier : public ResolveRequest::Delegate { public: - explicit DeDupeRequestsVerifier(CapturingHostMapper* mapper) - : count_a_(0), count_b_(0), mapper_(mapper) {} + explicit DeDupeRequestsVerifier(CapturingHostResolverProc* resolver_proc) + : count_a_(0), count_b_(0), resolver_proc_(resolver_proc) {} // The test does 5 resolves (which can complete in any order). virtual void OnCompleted(ResolveRequest* resolve) { @@ -348,9 +360,9 @@ class DeDupeRequestsVerifier : public ResolveRequest::Delegate { EXPECT_EQ(2, count_a_); EXPECT_EQ(3, count_b_); - // The mapper should have been called only twice -- once with "a", once - // with "b". - std::vector<std::string> capture_list = mapper_->GetCaptureList(); + // The resolver_proc should have been called only twice -- once with "a", + // once with "b". + std::vector<std::string> capture_list = resolver_proc_->GetCaptureList(); EXPECT_EQ(2U, capture_list.size()); // End this test, we are done. @@ -361,24 +373,25 @@ class DeDupeRequestsVerifier : public ResolveRequest::Delegate { private: int count_a_; int count_b_; - CapturingHostMapper* mapper_; + CapturingHostResolverProc* resolver_proc_; DISALLOW_COPY_AND_ASSIGN(DeDupeRequestsVerifier); }; -TEST_F(HostResolverTest, DeDupeRequests) { - // Use a capturing mapper, since the verifier needs to know what calls - // reached Map(). Also, the capturing mapper is initially blocked. - scoped_refptr<CapturingHostMapper> mapper = new CapturingHostMapper(); - ScopedHostMapper scoped_mapper(mapper.get()); +TEST_F(HostResolverImplTest, DeDupeRequests) { + // Use a capturing resolver_proc, since the verifier needs to know what calls + // reached Resolve(). Also, the capturing resolver_proc is initially blocked. + scoped_refptr<CapturingHostResolverProc> resolver_proc = + new CapturingHostResolverProc(NULL); - scoped_refptr<net::HostResolver> host_resolver(new net::HostResolver); + scoped_refptr<net::HostResolver> host_resolver( + new HostResolverImpl(resolver_proc, kMaxCacheEntries, kMaxCacheAgeMs)); // The class will receive callbacks for when each resolve completes. It // checks that the right things happened. - DeDupeRequestsVerifier verifier(mapper.get()); + DeDupeRequestsVerifier verifier(resolver_proc.get()); - // Start 5 requests, duplicating hosts "a" and "b". Since the mapper is + // Start 5 requests, duplicating hosts "a" and "b". Since the resolver_proc is // blocked, these should all pile up until we signal it. ResolveRequest req1(host_resolver, "a", 80, &verifier); @@ -388,13 +401,13 @@ TEST_F(HostResolverTest, DeDupeRequests) { ResolveRequest req5(host_resolver, "b", 83, &verifier); // Ready, Set, GO!!! - mapper->Signal(); + resolver_proc->Signal(); // |verifier| will send quit message once all the requests have finished. MessageLoop::current()->Run(); } -// Helper class used by HostResolverTest.CancelMultipleRequests. +// Helper class used by HostResolverImplTest.CancelMultipleRequests. class CancelMultipleRequestsVerifier : public ResolveRequest::Delegate { public: CancelMultipleRequestsVerifier() {} @@ -415,19 +428,21 @@ class CancelMultipleRequestsVerifier : public ResolveRequest::Delegate { DISALLOW_COPY_AND_ASSIGN(CancelMultipleRequestsVerifier); }; -TEST_F(HostResolverTest, CancelMultipleRequests) { - // Use a capturing mapper, since the verifier needs to know what calls - // reached Map(). Also, the capturing mapper is initially blocked. - scoped_refptr<CapturingHostMapper> mapper = new CapturingHostMapper(); - ScopedHostMapper scoped_mapper(mapper.get()); +TEST_F(HostResolverImplTest, CancelMultipleRequests) { + // Use a capturing resolver_proc, since the verifier needs to know what calls + // reached Resolver(). Also, the capturing resolver_proc is initially + // blocked. + scoped_refptr<CapturingHostResolverProc> resolver_proc = + new CapturingHostResolverProc(NULL); - scoped_refptr<net::HostResolver> host_resolver(new net::HostResolver); + scoped_refptr<net::HostResolver> host_resolver( + new HostResolverImpl(resolver_proc, kMaxCacheEntries, kMaxCacheAgeMs)); // The class will receive callbacks for when each resolve completes. It // checks that the right things happened. CancelMultipleRequestsVerifier verifier; - // Start 5 requests, duplicating hosts "a" and "b". Since the mapper is + // Start 5 requests, duplicating hosts "a" and "b". Since the resolver_proc is // blocked, these should all pile up until we signal it. ResolveRequest req1(host_resolver, "a", 80, &verifier); @@ -443,13 +458,13 @@ TEST_F(HostResolverTest, CancelMultipleRequests) { req5.Cancel(); // Ready, Set, GO!!! - mapper->Signal(); + resolver_proc->Signal(); // |verifier| will send quit message once all the requests have finished. MessageLoop::current()->Run(); } -// Helper class used by HostResolverTest.CancelWithinCallback. +// Helper class used by HostResolverImplTest.CancelWithinCallback. class CancelWithinCallbackVerifier : public ResolveRequest::Delegate { public: CancelWithinCallbackVerifier() @@ -500,19 +515,21 @@ class CancelWithinCallbackVerifier : public ResolveRequest::Delegate { DISALLOW_COPY_AND_ASSIGN(CancelWithinCallbackVerifier); }; -TEST_F(HostResolverTest, CancelWithinCallback) { - // Use a capturing mapper, since the verifier needs to know what calls - // reached Map(). Also, the capturing mapper is initially blocked. - scoped_refptr<CapturingHostMapper> mapper = new CapturingHostMapper(); - ScopedHostMapper scoped_mapper(mapper.get()); +TEST_F(HostResolverImplTest, CancelWithinCallback) { + // Use a capturing resolver_proc, since the verifier needs to know what calls + // reached Resolver(). Also, the capturing resolver_proc is initially + // blocked. + scoped_refptr<CapturingHostResolverProc> resolver_proc = + new CapturingHostResolverProc(NULL); - scoped_refptr<net::HostResolver> host_resolver(new net::HostResolver); + scoped_refptr<net::HostResolver> host_resolver( + new HostResolverImpl(resolver_proc, kMaxCacheEntries, kMaxCacheAgeMs)); // The class will receive callbacks for when each resolve completes. It // checks that the right things happened. CancelWithinCallbackVerifier verifier; - // Start 4 requests, duplicating hosts "a". Since the mapper is + // Start 4 requests, duplicating hosts "a". Since the resolver_proc is // blocked, these should all pile up until we signal it. ResolveRequest req1(host_resolver, "a", 80, &verifier); @@ -524,13 +541,13 @@ TEST_F(HostResolverTest, CancelWithinCallback) { verifier.SetRequestsToCancel(&req2, &req3); // Ready, Set, GO!!! - mapper->Signal(); + resolver_proc->Signal(); // |verifier| will send quit message once all the requests have finished. MessageLoop::current()->Run(); } -// Helper class used by HostResolverTest.DeleteWithinCallback. +// Helper class used by HostResolverImplTest.DeleteWithinCallback. class DeleteWithinCallbackVerifier : public ResolveRequest::Delegate { public: // |host_resolver| is the resolver that the the resolve requests were started @@ -556,19 +573,21 @@ class DeleteWithinCallbackVerifier : public ResolveRequest::Delegate { DISALLOW_COPY_AND_ASSIGN(DeleteWithinCallbackVerifier); }; -TEST_F(HostResolverTest, DeleteWithinCallback) { - // Use a capturing mapper, since the verifier needs to know what calls - // reached Map(). Also, the capturing mapper is initially blocked. - scoped_refptr<CapturingHostMapper> mapper = new CapturingHostMapper(); - ScopedHostMapper scoped_mapper(mapper.get()); +TEST_F(HostResolverImplTest, DeleteWithinCallback) { + // Use a capturing resolver_proc, since the verifier needs to know what calls + // reached Resolver(). Also, the capturing resolver_proc is initially + // blocked. + scoped_refptr<CapturingHostResolverProc> resolver_proc = + new CapturingHostResolverProc(NULL); // The class will receive callbacks for when each resolve completes. It // checks that the right things happened. Note that the verifier holds the // only reference to |host_resolver|, so it can delete it within callback. - net::HostResolver* host_resolver = new net::HostResolver; + net::HostResolver* host_resolver = + new HostResolverImpl(resolver_proc, kMaxCacheEntries, kMaxCacheAgeMs); DeleteWithinCallbackVerifier verifier(host_resolver); - // Start 4 requests, duplicating hosts "a". Since the mapper is + // Start 4 requests, duplicating hosts "a". Since the resolver_proc is // blocked, these should all pile up until we signal it. ResolveRequest req1(host_resolver, "a", 80, &verifier); @@ -577,13 +596,13 @@ TEST_F(HostResolverTest, DeleteWithinCallback) { ResolveRequest req4(host_resolver, "a", 83, &verifier); // Ready, Set, GO!!! - mapper->Signal(); + resolver_proc->Signal(); // |verifier| will send quit message once all the requests have finished. MessageLoop::current()->Run(); } -// Helper class used by HostResolverTest.StartWithinCallback. +// Helper class used by HostResolverImplTest.StartWithinCallback. class StartWithinCallbackVerifier : public ResolveRequest::Delegate { public: StartWithinCallbackVerifier() : num_requests_(0) {} @@ -609,20 +628,22 @@ class StartWithinCallbackVerifier : public ResolveRequest::Delegate { DISALLOW_COPY_AND_ASSIGN(StartWithinCallbackVerifier); }; -TEST_F(HostResolverTest, StartWithinCallback) { - // Use a capturing mapper, since the verifier needs to know what calls - // reached Map(). Also, the capturing mapper is initially blocked. - scoped_refptr<CapturingHostMapper> mapper = new CapturingHostMapper(); - ScopedHostMapper scoped_mapper(mapper.get()); +TEST_F(HostResolverImplTest, StartWithinCallback) { + // Use a capturing resolver_proc, since the verifier needs to know what calls + // reached Resolver(). Also, the capturing resolver_proc is initially + // blocked. + scoped_refptr<CapturingHostResolverProc> resolver_proc = + new CapturingHostResolverProc(NULL); // Turn off caching for this host resolver. - scoped_refptr<net::HostResolver> host_resolver(new net::HostResolver(0, 0)); + scoped_refptr<net::HostResolver> host_resolver( + new HostResolverImpl(resolver_proc, 0, 0)); // The class will receive callbacks for when each resolve completes. It // checks that the right things happened. StartWithinCallbackVerifier verifier; - // Start 4 requests, duplicating hosts "a". Since the mapper is + // Start 4 requests, duplicating hosts "a". Since the resolver_proc is // blocked, these should all pile up until we signal it. ResolveRequest req1(host_resolver, "a", 80, &verifier); @@ -631,13 +652,13 @@ TEST_F(HostResolverTest, StartWithinCallback) { ResolveRequest req4(host_resolver, "a", 83, &verifier); // Ready, Set, GO!!! - mapper->Signal(); + resolver_proc->Signal(); // |verifier| will send quit message once all the requests have finished. MessageLoop::current()->Run(); } -// Helper class used by HostResolverTest.BypassCache. +// Helper class used by HostResolverImplTest.BypassCache. class BypassCacheVerifier : public ResolveRequest::Delegate { public: BypassCacheVerifier() {} @@ -679,8 +700,9 @@ class BypassCacheVerifier : public ResolveRequest::Delegate { DISALLOW_COPY_AND_ASSIGN(BypassCacheVerifier); }; -TEST_F(HostResolverTest, BypassCache) { - scoped_refptr<net::HostResolver> host_resolver(new net::HostResolver); +TEST_F(HostResolverImplTest, BypassCache) { + scoped_refptr<net::HostResolver> host_resolver( + new HostResolverImpl(NULL, kMaxCacheEntries, kMaxCacheAgeMs)); // The class will receive callbacks for when each resolve completes. It // checks that the right things happened. @@ -762,8 +784,9 @@ class CapturingObserver : public net::HostResolver::Observer { // Test that registering, unregistering, and notifying of observers works. // Does not test the cancellation notification since all resolves are // synchronous. -TEST_F(HostResolverTest, Observers) { - scoped_refptr<net::HostResolver> host_resolver(new net::HostResolver); +TEST_F(HostResolverImplTest, Observers) { + scoped_refptr<net::HostResolver> host_resolver( + new HostResolverImpl(NULL, kMaxCacheEntries, kMaxCacheAgeMs)); CapturingObserver observer; @@ -829,11 +852,12 @@ TEST_F(HostResolverTest, Observers) { // cancelled. There are two ways to cancel a request: // (1) Delete the HostResolver while job is outstanding. // (2) Call HostResolver::CancelRequest() while a request is outstanding. -TEST_F(HostResolverTest, CancellationObserver) { +TEST_F(HostResolverImplTest, CancellationObserver) { CapturingObserver observer; { // Create a host resolver and attach an observer. - scoped_refptr<net::HostResolver> host_resolver(new net::HostResolver); + scoped_refptr<net::HostResolver> host_resolver( + new HostResolverImpl(NULL, kMaxCacheEntries, kMaxCacheAgeMs)); host_resolver->AddObserver(&observer); TestCompletionCallback callback; @@ -844,7 +868,7 @@ TEST_F(HostResolverTest, CancellationObserver) { // Start an async resolve for (host1:70). net::HostResolver::RequestInfo info1("host1", 70); - net::HostResolver::Request* req = NULL; + net::HostResolver::RequestHandle req = NULL; net::AddressList addrlist; int rv = host_resolver->Resolve(info1, &addrlist, &callback, &req); EXPECT_EQ(net::ERR_IO_PENDING, rv); @@ -857,7 +881,7 @@ TEST_F(HostResolverTest, CancellationObserver) { EXPECT_TRUE(observer.start_log[0] == CapturingObserver::StartOrCancelEntry(0, info1)); - // Cancel the request (host mapper is blocked so it cant be finished yet). + // Cancel the request. host_resolver->CancelRequest(req); EXPECT_EQ(1U, observer.start_log.size()); diff --git a/net/base/host_resolver_proc.cc b/net/base/host_resolver_proc.cc new file mode 100644 index 0000000..585a33e --- /dev/null +++ b/net/base/host_resolver_proc.cc @@ -0,0 +1,180 @@ +// Copyright (c) 2006-2008 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/base/host_resolver_proc.h" + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <ws2tcpip.h> +#include <wspiapi.h> // Needed for Win2k compat. +#elif defined(OS_POSIX) +#include <netdb.h> +#include <sys/socket.h> +#endif +#if defined(OS_LINUX) +#include <resolv.h> +#endif + +#include "base/logging.h" +#include "base/time.h" +#include "net/base/address_list.h" +#include "net/base/net_errors.h" + +#if defined(OS_LINUX) +#include "base/singleton.h" +#include "base/thread_local_storage.h" +#endif + +namespace net { + +HostResolverProc* HostResolverProc::default_proc_ = NULL; + +HostResolverProc::HostResolverProc(HostResolverProc* previous) { + set_previous_proc(previous); + + // Implicitly fall-back to the global default procedure. + if (!previous) + set_previous_proc(default_proc_); +} + +// static +HostResolverProc* HostResolverProc::SetDefault(HostResolverProc* proc) { + HostResolverProc* old = default_proc_; + default_proc_ = proc; + return old; +} + +// static +HostResolverProc* HostResolverProc::GetDefault() { + return default_proc_; +} + +int HostResolverProc::ResolveUsingPrevious(const std::string& host, + AddressList* addrlist) { + if (previous_proc_) + return previous_proc_->Resolve(host, addrlist); + + // Final fallback is the system resolver. + return SystemHostResolverProc(host, addrlist); +} + +#if defined(OS_LINUX) +// On Linux changes to /etc/resolv.conf can go unnoticed thus resulting in +// DNS queries failing either because nameservers are unknown on startup +// or because nameserver info has changed as a result of e.g. connecting to +// a new network. Some distributions patch glibc to stat /etc/resolv.conf +// to try to automatically detect such changes but these patches are not +// universal and even patched systems such as Jaunty appear to need calls +// to res_ninit to reload the nameserver information in different threads. +// +// We adopt the Mozilla solution here which is to call res_ninit when +// lookups fail and to rate limit the reloading to once per second per +// thread. + +// Keep a timer per calling thread to rate limit the calling of res_ninit. +class DnsReloadTimer { + public: + DnsReloadTimer() { + tls_index_.Initialize(SlotReturnFunction); + } + + ~DnsReloadTimer() { } + + // Check if the timer for the calling thread has expired. When no + // timer exists for the calling thread, create one. + bool Expired() { + const base::TimeDelta kRetryTime = base::TimeDelta::FromSeconds(1); + base::TimeTicks now = base::TimeTicks::Now(); + base::TimeTicks* timer_ptr = + static_cast<base::TimeTicks*>(tls_index_.Get()); + + if (!timer_ptr) { + timer_ptr = new base::TimeTicks(); + *timer_ptr = base::TimeTicks::Now(); + tls_index_.Set(timer_ptr); + // Return true to reload dns info on the first call for each thread. + return true; + } else if (now - *timer_ptr > kRetryTime) { + *timer_ptr = now; + return true; + } else { + return false; + } + } + + // Free the allocated timer. + static void SlotReturnFunction(void* data) { + base::TimeTicks* tls_data = static_cast<base::TimeTicks*>(data); + delete tls_data; + } + + private: + // We use thread local storage to identify which base::TimeTicks to + // interact with. + static ThreadLocalStorage::Slot tls_index_ ; + + DISALLOW_COPY_AND_ASSIGN(DnsReloadTimer); +}; + +// A TLS slot to the TimeTicks for the current thread. +// static +ThreadLocalStorage::Slot DnsReloadTimer::tls_index_(base::LINKER_INITIALIZED); + +#endif // defined(OS_LINUX) + +int SystemHostResolverProc(const std::string& host, AddressList* addrlist) { + struct addrinfo* ai = NULL; + struct addrinfo hints = {0}; + 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. + hints.ai_flags = 0; +#else + hints.ai_flags = AI_ADDRCONFIG; +#endif + + // Restrict result set to only this socket type to avoid duplicates. + hints.ai_socktype = SOCK_STREAM; + + int err = getaddrinfo(host.c_str(), NULL, &hints, &ai); +#if defined(OS_LINUX) + net::DnsReloadTimer* dns_timer = Singleton<net::DnsReloadTimer>::get(); + // If we fail, re-initialise the resolver just in case there have been any + // changes to /etc/resolv.conf and retry. See http://crbug.com/11380 for info. + if (err && dns_timer->Expired()) { + res_nclose(&_res); + if (!res_ninit(&_res)) + err = getaddrinfo(host.c_str(), NULL, &hints, &ai); + } +#endif + + if (err) + return ERR_NAME_NOT_RESOLVED; + + addrlist->Adopt(ai); + return OK; +} + +} // namespace net diff --git a/net/base/host_resolver_proc.h b/net/base/host_resolver_proc.h new file mode 100644 index 0000000..2690987 --- /dev/null +++ b/net/base/host_resolver_proc.h @@ -0,0 +1,69 @@ +// Copyright (c) 2009 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_BASE_HOST_RESOLVER_PROC_H_ +#define NET_BASE_HOST_RESOLVER_PROC_H_ + +#include <string> + +#include "base/ref_counted.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 HostResolverProc : public base::RefCountedThreadSafe<HostResolverProc> { + public: + explicit HostResolverProc(HostResolverProc* previous); + virtual ~HostResolverProc() {} + + // Resolves |host| to an address list. If successful returns OK and fills + // |addrlist| with a list of socket addresses. Otherwise returns a + // network error code. + virtual int Resolve(const std::string& host, AddressList* addrlist) = 0; + + protected: + // Asks the fallback procedure (if set) to do the resolve. + int ResolveUsingPrevious(const std::string& host, AddressList* addrlist); + + private: + friend class HostResolverImpl; + friend class MockHostResolver; + friend class ScopedDefaultHostResolverProc; + + // Sets the previous procedure in the chain. + void set_previous_proc(HostResolverProc* proc) { + previous_proc_ = 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(); + + private: + 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. +int SystemHostResolverProc(const std::string& host, AddressList* addrlist); + +} // namespace net + +#endif // NET_BASE_HOST_RESOLVER_PROC_H_ diff --git a/net/base/mock_host_resolver.cc b/net/base/mock_host_resolver.cc new file mode 100644 index 0000000..58ad552 --- /dev/null +++ b/net/base/mock_host_resolver.cc @@ -0,0 +1,154 @@ +// Copyright (c) 2009 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/base/mock_host_resolver.h" + +#include "base/string_util.h" +#include "base/platform_thread.h" +#include "base/ref_counted.h" +#include "net/base/net_errors.h" + +namespace net { + +MockHostResolver::MockHostResolver() { + Reset(NULL, 0, 0); +} + +int MockHostResolver::Resolve(const RequestInfo& info, + AddressList* addresses, + CompletionCallback* callback, + RequestHandle* out_req) { + return impl_->Resolve(info, addresses, callback, out_req); +} + +void MockHostResolver::CancelRequest(RequestHandle req) { + impl_->CancelRequest(req); +} + +void MockHostResolver::AddObserver(Observer* observer) { + impl_->AddObserver(observer); +} + +void MockHostResolver::RemoveObserver(Observer* observer) { + impl_->RemoveObserver(observer); +} + +void MockHostResolver::Shutdown() { + impl_->Shutdown(); +} + +void MockHostResolver::Reset(HostResolverProc* interceptor, + int max_cache_entries, + int max_cache_age_ms) { + // At the root of the chain, map everything to localhost. + scoped_refptr<RuleBasedHostResolverProc> catchall = + new RuleBasedHostResolverProc(NULL); + catchall->AddRule("*", "127.0.0.1"); + + // Next add a rules-based layer the use controls. + rules_ = new RuleBasedHostResolverProc(catchall); + + HostResolverProc* proc = rules_; + + // Lastly add the provided interceptor to the front of the chain. + if (interceptor) { + interceptor->set_previous_proc(proc); + proc = interceptor; + } + + impl_ = new HostResolverImpl(proc, max_cache_entries, max_cache_age_ms); +} + +//----------------------------------------------------------------------------- + +struct RuleBasedHostResolverProc::Rule { + std::string host_pattern; + std::string replacement; + int latency_ms; // In milliseconds. + bool direct; // if true, don't mangle hostname and ignore replacement + Rule(const std::string& host_pattern, const std::string& replacement) + : host_pattern(host_pattern), + replacement(replacement), + latency_ms(0), + direct(false) {} + Rule(const std::string& host_pattern, const std::string& replacement, + const int latency_ms) + : host_pattern(host_pattern), + replacement(replacement), + latency_ms(latency_ms), + direct(false) {} + Rule(const std::string& host_pattern, const std::string& replacement, + const bool direct) + : host_pattern(host_pattern), + replacement(replacement), + latency_ms(0), + direct(direct) {} +}; + +RuleBasedHostResolverProc::RuleBasedHostResolverProc(HostResolverProc* previous) + : HostResolverProc(previous) { +} + +RuleBasedHostResolverProc::~RuleBasedHostResolverProc() { +} + +void RuleBasedHostResolverProc::AddRule(const std::string& host_pattern, + const std::string& replacement) { + rules_.push_back(Rule(host_pattern, replacement)); +} + +void RuleBasedHostResolverProc::AddRuleWithLatency( + const std::string& host_pattern, + const std::string& replacement, int latency_ms) { + rules_.push_back(Rule(host_pattern, replacement, latency_ms)); +} + +void RuleBasedHostResolverProc::AllowDirectLookup(const std::string& host) { + rules_.push_back(Rule(host, "", true)); +} + +void RuleBasedHostResolverProc::AddSimulatedFailure(const std::string& host) { + AddRule(host, ""); +} + +int RuleBasedHostResolverProc::Resolve(const std::string& host, + AddressList* addrlist) { + RuleList::iterator r; + for (r = rules_.begin(); r != rules_.end(); ++r) { + if (MatchPattern(host, r->host_pattern)) { + if (r->latency_ms != 0) { + PlatformThread::Sleep(r->latency_ms); + // Hmm, this seems unecessary. + r->latency_ms = 1; + } + const std::string& effective_host = r->direct ? host : r->replacement; + if (effective_host.empty()) + return ERR_NAME_NOT_RESOLVED; + return SystemHostResolverProc(effective_host, addrlist); + } + } + return ResolveUsingPrevious(host, addrlist); +} + +//----------------------------------------------------------------------------- + +ScopedDefaultHostResolverProc::ScopedDefaultHostResolverProc( + HostResolverProc* proc) : current_proc_(proc) { + previous_proc_ = HostResolverProc::SetDefault(current_proc_); + current_proc_->set_previous_proc(previous_proc_); +} + +ScopedDefaultHostResolverProc::~ScopedDefaultHostResolverProc() { + HostResolverProc* old_proc = HostResolverProc::SetDefault(previous_proc_); + // The lifetimes of multiple instances must be nested. + CHECK(old_proc == current_proc_); +} + +void ScopedDefaultHostResolverProc::Init(HostResolverProc* proc) { + current_proc_ = proc; + previous_proc_ = HostResolverProc::SetDefault(current_proc_); + current_proc_->set_previous_proc(previous_proc_); +} + +} // namespace net diff --git a/net/base/mock_host_resolver.h b/net/base/mock_host_resolver.h new file mode 100644 index 0000000..ee83105 --- /dev/null +++ b/net/base/mock_host_resolver.h @@ -0,0 +1,143 @@ +// Copyright (c) 2009 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_BASE_MOCK_HOST_RESOLVER_H_ +#define NET_BASE_MOCK_HOST_RESOLVER_H_ + +#include <list> + +#include "base/waitable_event.h" +#include "net/base/host_resolver_impl.h" +#include "net/base/host_resolver_proc.h" + +namespace net { + +class RuleBasedHostResolverProc; + +// In most cases, it is important that unit tests avoid 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. +class MockHostResolver : public HostResolver { + public: + // Creates a MockHostResolver that does NOT cache entries + // (the HostResolverProc will be called for every lookup). If you need + // caching behavior, call Reset() with non-zero cache size. + MockHostResolver(); + + virtual ~MockHostResolver() {} + + // HostResolver methods: + virtual int Resolve(const RequestInfo& info, AddressList* addresses, + CompletionCallback* callback, RequestHandle* out_req); + virtual void CancelRequest(RequestHandle req); + virtual void AddObserver(Observer* observer); + virtual void RemoveObserver(Observer* observer); + // TODO(eroman): temp hack for http://crbug.com/15513 + virtual void Shutdown(); + + RuleBasedHostResolverProc* rules() { return rules_; } + + // Resets the mock. + void Reset(HostResolverProc* interceptor, + int max_cache_entries, + int max_cache_age_ms); + + private: + scoped_refptr<HostResolverImpl> impl_; + scoped_refptr<RuleBasedHostResolverProc> rules_; +}; + +// 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); + ~RuleBasedHostResolverProc(); + + // 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); + + 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); + + // HostResolverProc methods: + virtual int Resolve(const std::string& host, AddressList* addrlist); + + private: + struct Rule; + typedef std::list<Rule> RuleList; + RuleList rules_; +}; + +// Using WaitingHostResolverProc you can simulate very long lookups. +class WaitingHostResolverProc : public HostResolverProc { + public: + explicit WaitingHostResolverProc(HostResolverProc* previous) + : HostResolverProc(previous), event_(false, false) {} + + void Signal() { + event_.Signal(); + } + + // HostResolverProc methods: + virtual int Resolve(const std::string& host, AddressList* addrlist) { + event_.Wait(); + return ResolveUsingPrevious(host, addrlist); + } + + base::WaitableEvent event_; +}; + +// This class sets the HostResolverProc for a particular scope. If there are +// multiple ScopedDefaultHostResolverProc in existence, then the last one +// allocated will be used. However, if it does not provide a matching rule, +// then it should delegate to the previously set HostResolverProc. +// +// 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_BASE_MOCK_HOST_RESOLVER_H_ diff --git a/net/base/run_all_unittests.cc b/net/base/run_all_unittests.cc index 6c60e2d..c4c4df7 100644 --- a/net/base/run_all_unittests.cc +++ b/net/base/run_all_unittests.cc @@ -30,7 +30,7 @@ #include "base/message_loop.h" #include "base/ref_counted.h" #include "base/test_suite.h" -#include "net/base/host_resolver_unittest.h" +#include "net/base/mock_host_resolver.h" class NetTestSuite : public TestSuite { public: @@ -40,12 +40,12 @@ class NetTestSuite : public TestSuite { virtual void Initialize() { TestSuite::Initialize(); - host_mapper_ = new net::RuleBasedHostMapper(); - scoped_host_mapper_.Init(host_mapper_.get()); + host_resolver_proc_ = new net::RuleBasedHostResolverProc(NULL); + scoped_host_resolver_proc_.Init(host_resolver_proc_.get()); // In case any attempts are made to resolve host names, force them all to // be mapped to localhost. This prevents DNS queries from being sent in // the process of running these unit tests. - host_mapper_->AddRule("*", "127.0.0.1"); + host_resolver_proc_->AddRule("*", "127.0.0.1"); message_loop_.reset(new MessageLoopForIO()); } @@ -60,8 +60,8 @@ class NetTestSuite : public TestSuite { private: scoped_ptr<MessageLoop> message_loop_; - scoped_refptr<net::RuleBasedHostMapper> host_mapper_; - net::ScopedHostMapper scoped_host_mapper_; + scoped_refptr<net::RuleBasedHostResolverProc> host_resolver_proc_; + net::ScopedDefaultHostResolverProc scoped_host_resolver_proc_; }; int main(int argc, char** argv) { |