diff options
Diffstat (limited to 'net/base/host_resolver.cc')
-rw-r--r-- | net/base/host_resolver.cc | 608 |
1 files changed, 2 insertions, 606 deletions
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_*|. |