| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/base/dnsrr_resolver.h" |
| |
| #if defined(OS_POSIX) |
| #include <netinet/in.h> |
| #include <resolv.h> |
| #endif |
| |
| #if defined(OS_WIN) |
| #include <windns.h> |
| #endif |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/location.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/memory/singleton.h" |
| #include "base/message_loop.h" |
| #include "base/stl_util.h" |
| #include "base/string_piece.h" |
| #include "base/synchronization/lock.h" |
| #include "base/threading/worker_pool.h" |
| #include "net/base/dns_reloader.h" |
| #include "net/base/dns_util.h" |
| #include "net/base/net_errors.h" |
| |
| // Life of a query: |
| // |
| // DnsRRResolver RRResolverJob RRResolverWorker ... Handle |
| // | (origin loop) (worker loop) |
| // | |
| // Resolve() |
| // |---->-------------------<creates> |
| // | |
| // |---->----<creates> |
| // | |
| // |---->---------------------------------------------------<creates> |
| // | |
| // |---->--------------------Start |
| // | | |
| // | PostTask |
| // | |
| // | <starts resolving> |
| // |---->-----AddHandle | |
| // | |
| // | |
| // | |
| // Finish |
| // | |
| // PostTask |
| // |
| // | |
| // DoReply |
| // |----<-----------------------| |
| // HandleResult |
| // | |
| // |---->-----HandleResult |
| // | |
| // |------>-----------------------------------Post |
| // |
| // |
| // |
| // A cache hit: |
| // |
| // DnsRRResolver Handle |
| // | |
| // Resolve() |
| // |---->------------------------<creates> |
| // | |
| // | |
| // PostTask |
| // |
| // (MessageLoop cycles) |
| // |
| // Post |
| |
| namespace net { |
| |
| #if defined(OS_WIN) |
| // DnsRRIsParsedByWindows returns true if Windows knows how to parse the given |
| // RR type. RR data is returned in a DNS_RECORD structure which may be raw (if |
| // Windows doesn't parse it) or may be a parse result. It's unclear how this |
| // API is intended to evolve in the future. If Windows adds support for new RR |
| // types in a future version a client which expected raw data will break. |
| // See http://msdn.microsoft.com/en-us/library/ms682082(v=vs.85).aspx |
| static bool DnsRRIsParsedByWindows(uint16 rrtype) { |
| // We only cover the types which are defined in dns_util.h |
| switch (rrtype) { |
| case kDNS_CNAME: |
| case kDNS_TXT: |
| case kDNS_DS: |
| case kDNS_RRSIG: |
| case kDNS_DNSKEY: |
| return true; |
| default: |
| return false; |
| } |
| } |
| #endif |
| |
| // kMaxCacheEntries is the number of RRResponse objects that we'll cache. |
| static const unsigned kMaxCacheEntries = 32; |
| // kNegativeTTLSecs is the number of seconds for which we'll cache a negative |
| // cache entry. |
| static const unsigned kNegativeTTLSecs = 60; |
| |
| RRResponse::RRResponse() |
| : ttl(0), dnssec(false), negative(false) { |
| } |
| |
| RRResponse::~RRResponse() {} |
| |
| class RRResolverHandle { |
| public: |
| RRResolverHandle(const CompletionCallback& callback, RRResponse* response) |
| : callback_(callback), |
| response_(response) { |
| } |
| |
| // Cancel ensures that the result callback will never be made. |
| void Cancel() { |
| callback_.Reset(); |
| response_ = NULL; |
| } |
| |
| // Post copies the contents of |response| to the caller's RRResponse and |
| // calls the callback. |
| void Post(int rv, const RRResponse* response) { |
| if (!callback_.is_null()) { |
| if (response_ && response) |
| *response_ = *response; |
| callback_.Run(rv); |
| } |
| delete this; |
| } |
| |
| private: |
| CompletionCallback callback_; |
| RRResponse* response_; |
| }; |
| |
| |
| // RRResolverWorker runs on a worker thread and takes care of the blocking |
| // process of performing the DNS resolution. |
| class RRResolverWorker { |
| public: |
| RRResolverWorker(const std::string& name, uint16 rrtype, uint16 flags, |
| DnsRRResolver* dnsrr_resolver) |
| : name_(name), |
| rrtype_(rrtype), |
| flags_(flags), |
| origin_loop_(MessageLoop::current()), |
| dnsrr_resolver_(dnsrr_resolver), |
| canceled_(false), |
| result_(ERR_UNEXPECTED) { |
| } |
| |
| bool Start() { |
| DCHECK_EQ(MessageLoop::current(), origin_loop_); |
| |
| return base::WorkerPool::PostTask( |
| FROM_HERE, base::Bind(&RRResolverWorker::Run, base::Unretained(this)), |
| true /* task is slow */); |
| } |
| |
| // Cancel is called from the origin loop when the DnsRRResolver is getting |
| // deleted. |
| void Cancel() { |
| DCHECK_EQ(MessageLoop::current(), origin_loop_); |
| base::AutoLock locked(lock_); |
| canceled_ = true; |
| } |
| |
| private: |
| |
| #if defined(OS_ANDROID) |
| |
| void Run() { |
| if (HandleTestCases()) { |
| Finish(); |
| return; |
| } |
| NOTIMPLEMENTED(); |
| } |
| |
| #elif defined(OS_POSIX) |
| |
| void Run() { |
| // Runs on a worker thread. |
| |
| if (HandleTestCases()) { |
| Finish(); |
| return; |
| } |
| |
| bool r = true; |
| #if defined(OS_MACOSX) || defined(OS_OPENBSD) |
| if ((_res.options & RES_INIT) == 0) { |
| #if defined(OS_OPENBSD) |
| if (res_init() != 0) |
| #else |
| if (res_ninit(&_res) != 0) |
| #endif |
| r = false; |
| } |
| #else |
| DnsReloaderMaybeReload(); |
| #endif |
| |
| if (r) { |
| unsigned long saved_options = _res.options; |
| r = Do(); |
| _res.options = saved_options; |
| } |
| |
| response_.fetch_time = base::Time::Now(); |
| |
| if (r) { |
| result_ = OK; |
| } else { |
| result_ = ERR_NAME_NOT_RESOLVED; |
| response_.negative = true; |
| response_.ttl = kNegativeTTLSecs; |
| } |
| |
| Finish(); |
| } |
| |
| bool Do() { |
| // For DNSSEC, a 4K buffer is suggested |
| static const unsigned kMaxDNSPayload = 4096; |
| |
| #ifndef RES_USE_DNSSEC |
| // Some versions of libresolv don't have support for the DO bit. In this |
| // case, we proceed without it. |
| static const int RES_USE_DNSSEC = 0; |
| #endif |
| |
| #ifndef RES_USE_EDNS0 |
| // Some versions of glibc are so old that they don't support EDNS0 either. |
| // http://code.google.com/p/chromium/issues/detail?id=51676 |
| static const int RES_USE_EDNS0 = 0; |
| #endif |
| |
| // We set the options explicitly. Note that this removes several default |
| // options: RES_DEFNAMES and RES_DNSRCH (see res_init(3)). |
| _res.options = RES_INIT | RES_RECURSE | RES_USE_EDNS0 | RES_USE_DNSSEC; |
| uint8 answer[kMaxDNSPayload]; |
| int len = res_search(name_.c_str(), kClassIN, rrtype_, answer, |
| sizeof(answer)); |
| if (len == -1) |
| return false; |
| |
| return response_.ParseFromResponse(answer, len, rrtype_); |
| } |
| |
| #else // OS_WIN |
| |
| void Run() { |
| if (HandleTestCases()) { |
| Finish(); |
| return; |
| } |
| |
| // See http://msdn.microsoft.com/en-us/library/ms682016(v=vs.85).aspx |
| PDNS_RECORD record = NULL; |
| DNS_STATUS status = |
| DnsQuery_A(name_.c_str(), rrtype_, DNS_QUERY_STANDARD, |
| NULL /* pExtra (reserved) */, &record, NULL /* pReserved */); |
| response_.fetch_time = base::Time::Now(); |
| response_.name = name_; |
| response_.dnssec = false; |
| response_.ttl = 0; |
| |
| if (status != 0) { |
| response_.negative = true; |
| result_ = ERR_NAME_NOT_RESOLVED; |
| } else { |
| response_.negative = false; |
| result_ = OK; |
| for (DNS_RECORD* cur = record; cur; cur = cur->pNext) { |
| if (cur->wType == rrtype_) { |
| response_.ttl = record->dwTtl; |
| // Windows will parse some types of resource records. If we want one |
| // of these types then we have to reserialise the record. |
| switch (rrtype_) { |
| case kDNS_TXT: { |
| // http://msdn.microsoft.com/en-us/library/ms682109(v=vs.85).aspx |
| const DNS_TXT_DATA* txt = &cur->Data.TXT; |
| std::string rrdata; |
| |
| for (DWORD i = 0; i < txt->dwStringCount; i++) { |
| // Although the string is typed as a PWSTR, it's actually just |
| // an ASCII byte-string. Also, the string must be < 256 |
| // elements because the length in the DNS packet is a single |
| // byte. |
| const char* s = reinterpret_cast<char*>(txt->pStringArray[i]); |
| size_t len = strlen(s); |
| DCHECK_LT(len, 256u); |
| char len8 = static_cast<char>(len); |
| rrdata.push_back(len8); |
| rrdata += s; |
| } |
| response_.rrdatas.push_back(rrdata); |
| break; |
| } |
| default: |
| if (DnsRRIsParsedByWindows(rrtype_)) { |
| // Windows parses this type, but we don't have code to unparse |
| // it. |
| NOTREACHED() << "you need to add code for the RR type here"; |
| response_.negative = true; |
| result_ = ERR_INVALID_ARGUMENT; |
| } else { |
| // This type is given to us raw. |
| response_.rrdatas.push_back( |
| std::string(reinterpret_cast<char*>(&cur->Data), |
| cur->wDataLength)); |
| } |
| } |
| } |
| } |
| } |
| |
| DnsRecordListFree(record, DnsFreeRecordList); |
| Finish(); |
| } |
| |
| #endif // OS_WIN |
| |
| // HandleTestCases stuffs in magic test values in the event that the query is |
| // from a unittest. |
| bool HandleTestCases() { |
| if (rrtype_ == kDNS_TESTING) { |
| response_.fetch_time = base::Time::Now(); |
| |
| if (name_ == "www.testing.notatld") { |
| response_.ttl = 86400; |
| response_.negative = false; |
| response_.rrdatas.push_back("goats!"); |
| result_ = OK; |
| return true; |
| } else if (name_ == "nx.testing.notatld") { |
| response_.negative = true; |
| result_ = ERR_NAME_NOT_RESOLVED; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // DoReply runs on the origin thread. |
| void DoReply() { |
| DCHECK_EQ(MessageLoop::current(), origin_loop_); |
| { |
| // We lock here because the worker thread could still be in Finished, |
| // after the PostTask, but before unlocking |lock_|. If we do not lock in |
| // this case, we will end up deleting a locked Lock, which can lead to |
| // memory leaks or worse errors. |
| base::AutoLock locked(lock_); |
| if (!canceled_) |
| dnsrr_resolver_->HandleResult(name_, rrtype_, result_, response_); |
| } |
| delete this; |
| } |
| |
| void Finish() { |
| // Runs on the worker thread. |
| // We assume that the origin loop outlives the DnsRRResolver. If the |
| // DnsRRResolver is deleted, it will call Cancel on us. If it does so |
| // before the Acquire, we'll delete ourselves and return. If it's trying to |
| // do so concurrently, then it'll block on the lock and we'll call PostTask |
| // while the DnsRRResolver (and therefore the MessageLoop) is still alive. |
| // If it does so after this function, we assume that the MessageLoop will |
| // process pending tasks. In which case we'll notice the |canceled_| flag |
| // in DoReply. |
| |
| bool canceled; |
| { |
| base::AutoLock locked(lock_); |
| canceled = canceled_; |
| if (!canceled) { |
| origin_loop_->PostTask(FROM_HERE, base::Bind( |
| &RRResolverWorker::DoReply, base::Unretained(this))); |
| } |
| } |
| |
| if (canceled) |
| delete this; |
| } |
| |
| const std::string name_; |
| const uint16 rrtype_; |
| const uint16 flags_; |
| MessageLoop* const origin_loop_; |
| DnsRRResolver* const dnsrr_resolver_; |
| |
| base::Lock lock_; |
| bool canceled_; |
| |
| int result_; |
| RRResponse response_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RRResolverWorker); |
| }; |
| |
| bool RRResponse::HasExpired(const base::Time current_time) const { |
| const base::TimeDelta delta(base::TimeDelta::FromSeconds(ttl)); |
| const base::Time expiry = fetch_time + delta; |
| return current_time >= expiry; |
| } |
| |
| #if defined(OS_POSIX) && !defined(OS_ANDROID) |
| bool RRResponse::ParseFromResponse(const uint8* p, unsigned len, |
| uint16 rrtype_requested) { |
| name.clear(); |
| ttl = 0; |
| dnssec = false; |
| negative = false; |
| rrdatas.clear(); |
| signatures.clear(); |
| |
| // RFC 1035 section 4.4.1 |
| uint8 flags2; |
| DnsResponseBuffer buf(p, len); |
| if (!buf.Skip(2) || // skip id |
| !buf.Skip(1) || // skip first flags byte |
| !buf.U8(&flags2)) { |
| return false; |
| } |
| |
| // Bit 5 is the Authenticated Data (AD) bit. See |
| // http://tools.ietf.org/html/rfc2535#section-6.1 |
| if (flags2 & 32) { |
| // AD flag is set. We'll trust it if it came from a local nameserver. |
| // Currently the resolv structure is IPv4 only, so we can't test for IPv6 |
| // loopback addresses. |
| if (_res.nscount == 1 && |
| memcmp(&_res.nsaddr_list[0].sin_addr, |
| "\x7f\x00\x00\x01" /* 127.0.0.1 */, 4) == 0) { |
| dnssec = true; |
| } |
| } |
| |
| uint16 query_count, answer_count, authority_count, additional_count; |
| if (!buf.U16(&query_count) || |
| !buf.U16(&answer_count) || |
| !buf.U16(&authority_count) || |
| !buf.U16(&additional_count)) { |
| return false; |
| } |
| |
| if (query_count != 1) |
| return false; |
| |
| uint16 type, klass; |
| if (!buf.DNSName(NULL) || |
| !buf.U16(&type) || |
| !buf.U16(&klass) || |
| type != rrtype_requested || |
| klass != kClassIN) { |
| return false; |
| } |
| |
| if (answer_count < 1) |
| return false; |
| |
| for (uint32 i = 0; i < answer_count; i++) { |
| std::string* name = NULL; |
| if (i == 0) |
| name = &this->name; |
| uint32 ttl; |
| uint16 rrdata_len; |
| if (!buf.DNSName(name) || |
| !buf.U16(&type) || |
| !buf.U16(&klass) || |
| !buf.U32(&ttl) || |
| !buf.U16(&rrdata_len)) { |
| return false; |
| } |
| |
| base::StringPiece rrdata; |
| if (!buf.Block(&rrdata, rrdata_len)) |
| return false; |
| |
| if (klass == kClassIN && type == rrtype_requested) { |
| if (i == 0) |
| this->ttl = ttl; |
| rrdatas.push_back(std::string(rrdata.data(), rrdata.size())); |
| } else if (klass == kClassIN && type == kDNS_RRSIG) { |
| signatures.push_back(std::string(rrdata.data(), rrdata.size())); |
| } |
| } |
| return true; |
| } |
| #endif // defined(OS_POSIX) && !defined(OS_ANDROID) |
| |
| |
| // An RRResolverJob is a one-to-one counterpart of an RRResolverWorker. It |
| // lives only on the DnsRRResolver's origin message loop. |
| class RRResolverJob { |
| public: |
| explicit RRResolverJob(RRResolverWorker* worker) |
| : worker_(worker) { |
| } |
| |
| ~RRResolverJob() { |
| Cancel(ERR_ABORTED); |
| } |
| |
| void AddHandle(RRResolverHandle* handle) { |
| handles_.push_back(handle); |
| } |
| |
| void HandleResult(int result, const RRResponse& response) { |
| worker_ = NULL; |
| PostAll(result, &response); |
| } |
| |
| void Cancel(int result) { |
| if (worker_) { |
| worker_->Cancel(); |
| worker_ = NULL; |
| PostAll(result, NULL); |
| } |
| } |
| |
| private: |
| void PostAll(int result, const RRResponse* response) { |
| std::vector<RRResolverHandle*> handles; |
| handles_.swap(handles); |
| |
| for (std::vector<RRResolverHandle*>::iterator |
| i = handles.begin(); i != handles.end(); i++) { |
| (*i)->Post(result, response); |
| // Post() causes the RRResolverHandle to delete itself. |
| } |
| } |
| |
| std::vector<RRResolverHandle*> handles_; |
| RRResolverWorker* worker_; |
| }; |
| |
| |
| DnsRRResolver::DnsRRResolver() |
| : requests_(0), |
| cache_hits_(0), |
| inflight_joins_(0), |
| in_destructor_(false) { |
| } |
| |
| DnsRRResolver::~DnsRRResolver() { |
| DCHECK(!in_destructor_); |
| in_destructor_ = true; |
| STLDeleteValues(&inflight_); |
| } |
| |
| intptr_t DnsRRResolver::Resolve(const std::string& name, uint16 rrtype, |
| uint16 flags, |
| const CompletionCallback& callback, |
| RRResponse* response, |
| int priority /* ignored */, |
| const BoundNetLog& netlog /* ignored */) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(!in_destructor_); |
| |
| if (callback.is_null() || !response || name.empty()) |
| return kInvalidHandle; |
| |
| // Don't allow queries of type ANY |
| if (rrtype == kDNS_ANY) |
| return kInvalidHandle; |
| |
| requests_++; |
| |
| const std::pair<std::string, uint16> key(make_pair(name, rrtype)); |
| // First check the cache. |
| std::map<std::pair<std::string, uint16>, RRResponse>::iterator i; |
| i = cache_.find(key); |
| if (i != cache_.end()) { |
| if (!i->second.HasExpired(base::Time::Now())) { |
| int error; |
| if (i->second.negative) { |
| error = ERR_NAME_NOT_RESOLVED; |
| } else { |
| error = OK; |
| *response = i->second; |
| } |
| RRResolverHandle* handle = new RRResolverHandle( |
| callback, NULL /* no response pointer because we've already filled */ |
| /* it in */); |
| cache_hits_++; |
| // We need a typed NULL pointer in order to make the templates work out. |
| static const RRResponse* kNoResponse = NULL; |
| MessageLoop::current()->PostTask( |
| FROM_HERE, base::Bind( |
| &RRResolverHandle::Post, base::Unretained(handle), error, |
| kNoResponse)); |
| return reinterpret_cast<intptr_t>(handle); |
| } else { |
| // entry has expired. |
| cache_.erase(i); |
| } |
| } |
| |
| // No cache hit. See if a request is currently in flight. |
| RRResolverJob* job; |
| std::map<std::pair<std::string, uint16>, RRResolverJob*>::const_iterator j; |
| j = inflight_.find(key); |
| if (j != inflight_.end()) { |
| // The request is in flight already. We'll just attach our callback. |
| inflight_joins_++; |
| job = j->second; |
| } else { |
| // Need to make a new request. |
| RRResolverWorker* worker = new RRResolverWorker(name, rrtype, flags, this); |
| job = new RRResolverJob(worker); |
| inflight_.insert(make_pair(key, job)); |
| if (!worker->Start()) { |
| inflight_.erase(key); |
| delete job; |
| delete worker; |
| return kInvalidHandle; |
| } |
| } |
| |
| RRResolverHandle* handle = new RRResolverHandle(callback, response); |
| job->AddHandle(handle); |
| return reinterpret_cast<intptr_t>(handle); |
| } |
| |
| void DnsRRResolver::CancelResolve(intptr_t h) { |
| DCHECK(CalledOnValidThread()); |
| RRResolverHandle* handle = reinterpret_cast<RRResolverHandle*>(h); |
| handle->Cancel(); |
| } |
| |
| void DnsRRResolver::OnIPAddressChanged() { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(!in_destructor_); |
| |
| std::map<std::pair<std::string, uint16>, RRResolverJob*> inflight; |
| inflight.swap(inflight_); |
| cache_.clear(); |
| |
| std::map<std::pair<std::string, uint16>, RRResolverJob*>::iterator it; |
| for (it = inflight.begin(); it != inflight.end(); ++it) |
| it->second->Cancel(ERR_NETWORK_CHANGED); |
| STLDeleteValues(&inflight); |
| } |
| |
| // HandleResult is called on the origin message loop. |
| void DnsRRResolver::HandleResult(const std::string& name, uint16 rrtype, |
| int result, const RRResponse& response) { |
| DCHECK(CalledOnValidThread()); |
| |
| const std::pair<std::string, uint16> key(std::make_pair(name, rrtype)); |
| |
| DCHECK_GE(kMaxCacheEntries, 1u); |
| DCHECK_LE(cache_.size(), kMaxCacheEntries); |
| if (cache_.size() == kMaxCacheEntries) { |
| // need to remove an element of the cache. |
| const base::Time current_time(base::Time::Now()); |
| std::map<std::pair<std::string, uint16>, RRResponse>::iterator i, cur; |
| for (i = cache_.begin(); i != cache_.end(); ) { |
| cur = i++; |
| if (cur->second.HasExpired(current_time)) |
| cache_.erase(cur); |
| } |
| } |
| if (cache_.size() == kMaxCacheEntries) { |
| // if we didn't clear out any expired entries, we just remove the first |
| // element. Crummy but simple. |
| cache_.erase(cache_.begin()); |
| } |
| |
| cache_.insert(std::make_pair(key, response)); |
| |
| std::map<std::pair<std::string, uint16>, RRResolverJob*>::iterator j; |
| j = inflight_.find(key); |
| if (j == inflight_.end()) { |
| NOTREACHED(); |
| return; |
| } |
| RRResolverJob* job = j->second; |
| inflight_.erase(j); |
| |
| job->HandleResult(result, response); |
| delete job; |
| } |
| |
| } // namespace net |