diff options
author | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-02-23 15:51:34 +0000 |
---|---|---|
committer | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-02-23 15:51:34 +0000 |
commit | c284be3f28d9c9eea24b844c7aeb9c004ce2de9f (patch) | |
tree | 901986c10f09beb41d1a8a2454ff6a452d0a6bb6 /net | |
parent | deb40835d8e2f88901af6934b659f0eb89fa2177 (diff) | |
download | chromium_src-c284be3f28d9c9eea24b844c7aeb9c004ce2de9f.zip chromium_src-c284be3f28d9c9eea24b844c7aeb9c004ce2de9f.tar.gz chromium_src-c284be3f28d9c9eea24b844c7aeb9c004ce2de9f.tar.bz2 |
Revert "net: remove DnsRRResolver"
This reverts commit r114845.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@123247 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/base/dns_util.cc | 116 | ||||
-rw-r--r-- | net/base/dns_util.h | 37 | ||||
-rw-r--r-- | net/base/dnsrr_resolver.cc | 686 | ||||
-rw-r--r-- | net/base/dnsrr_resolver.h | 140 | ||||
-rw-r--r-- | net/base/dnsrr_resolver_unittest.cc | 211 | ||||
-rw-r--r-- | net/net.gyp | 3 |
6 files changed, 1193 insertions, 0 deletions
diff --git a/net/base/dns_util.cc b/net/base/dns_util.cc index 816095b..a49ada8 100644 --- a/net/base/dns_util.cc +++ b/net/base/dns_util.cc @@ -101,4 +101,120 @@ std::string TrimEndingDot(const base::StringPiece& host) { return host_trimmed.as_string(); } +bool DnsResponseBuffer::U8(uint8* v) { + if (len_ < 1) + return false; + *v = *p_; + p_++; + len_--; + return true; +} + +bool DnsResponseBuffer::U16(uint16* v) { + if (len_ < 2) + return false; + *v = static_cast<uint16>(p_[0]) << 8 | + static_cast<uint16>(p_[1]); + p_ += 2; + len_ -= 2; + return true; +} + +bool DnsResponseBuffer::U32(uint32* v) { + if (len_ < 4) + return false; + *v = static_cast<uint32>(p_[0]) << 24 | + static_cast<uint32>(p_[1]) << 16 | + static_cast<uint32>(p_[2]) << 8 | + static_cast<uint32>(p_[3]); + p_ += 4; + len_ -= 4; + return true; +} + +bool DnsResponseBuffer::Skip(unsigned n) { + if (len_ < n) + return false; + p_ += n; + len_ -= n; + return true; +} + +bool DnsResponseBuffer::Block(base::StringPiece* out, unsigned len) { + if (len_ < len) + return false; + *out = base::StringPiece(reinterpret_cast<const char*>(p_), len); + p_ += len; + len_ -= len; + return true; +} + +// DNSName parses a (possibly compressed) DNS name from the packet. If |name| +// is not NULL, then the name is written into it. See RFC 1035 section 4.1.4. +bool DnsResponseBuffer::DNSName(std::string* name) { + unsigned jumps = 0; + const uint8* p = p_; + unsigned len = len_; + + if (name) + name->clear(); + + for (;;) { + if (len < 1) + return false; + uint8 d = *p; + p++; + len--; + + // The two couple of bits of the length give the type of the length. It's + // either a direct length or a pointer to the remainder of the name. + if ((d & 0xc0) == 0xc0) { + // This limit matches the depth limit in djbdns. + if (jumps > 100) + return false; + if (len < 1) + return false; + uint16 offset = static_cast<uint16>(d) << 8 | + static_cast<uint16>(p[0]); + offset &= 0x3ff; + p++; + len--; + + if (jumps == 0) { + p_ = p; + len_ = len; + } + jumps++; + + if (offset >= packet_len_) + return false; + p = &packet_[offset]; + len = packet_len_ - offset; + } else if ((d & 0xc0) == 0) { + uint8 label_len = d; + if (len < label_len) + return false; + if (name && label_len) { + if (!name->empty()) + name->append("."); + name->append(reinterpret_cast<const char*>(p), label_len); + } + p += label_len; + len -= label_len; + + if (jumps == 0) { + p_ = p; + len_ = len; + } + + if (label_len == 0) + break; + } else { + return false; + } + } + + return true; +} + } // namespace net diff --git a/net/base/dns_util.h b/net/base/dns_util.h index edfe559..2bd3eda 100644 --- a/net/base/dns_util.h +++ b/net/base/dns_util.h @@ -42,13 +42,19 @@ static const uint16 kClassIN = 1; // DNS resource record types. See // http://www.iana.org/assignments/dns-parameters +// WARNING: if you're adding any new values here you may need to add them to +// dnsrr_resolver.cc:DnsRRIsParsedByWindows. +static const uint16 kDNS_A = 1; static const uint16 kDNS_CNAME = 5; static const uint16 kDNS_TXT = 16; +static const uint16 kDNS_AAAA = 28; static const uint16 kDNS_CERT = 37; static const uint16 kDNS_DS = 43; static const uint16 kDNS_RRSIG = 46; static const uint16 kDNS_DNSKEY = 48; +static const uint16 kDNS_ANY = 0xff; static const uint16 kDNS_CAA = 257; +static const uint16 kDNS_TESTING = 0xfffe; // in private use area. // http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml static const uint8 kDNSSEC_RSA_SHA1 = 5; @@ -59,6 +65,37 @@ static const uint8 kDNSSEC_RSA_SHA256 = 8; static const uint8 kDNSSEC_SHA1 = 1; static const uint8 kDNSSEC_SHA256 = 2; +// A Buffer is used for walking over a DNS response packet. +class DnsResponseBuffer { + public: + DnsResponseBuffer(const uint8* p, unsigned len) + : p_(p), + packet_(p), + len_(len), + packet_len_(len) { + } + + bool U8(uint8* v); + bool U16(uint16* v); + bool U32(uint32* v); + bool Skip(unsigned n); + + bool Block(base::StringPiece* out, unsigned len); + + // DNSName parses a (possibly compressed) DNS name from the packet. If |name| + // is not NULL, then the name is written into it. See RFC 1035 section 4.1.4. + bool DNSName(std::string* name); + + private: + const uint8* p_; + const uint8* const packet_; + unsigned len_; + const unsigned packet_len_; + + DISALLOW_COPY_AND_ASSIGN(DnsResponseBuffer); +}; + + } // namespace net #endif // NET_BASE_DNS_UTIL_H_ diff --git a/net/base/dnsrr_resolver.cc b/net/base/dnsrr_resolver.cc new file mode 100644 index 0000000..65ea8b6 --- /dev/null +++ b/net/base/dnsrr_resolver.cc @@ -0,0 +1,686 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/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() { + 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() { + if (worker_) { + worker_->Cancel(); + worker_ = NULL; + PostAll(ERR_ABORTED, NULL); + } + } + + void AddHandle(RRResolverHandle* handle) { + handles_.push_back(handle); + } + + void HandleResult(int result, const RRResponse& response) { + worker_ = NULL; + PostAll(result, &response); + } + + 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(); + + 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 diff --git a/net/base/dnsrr_resolver.h b/net/base/dnsrr_resolver.h new file mode 100644 index 0000000..297fc43 --- /dev/null +++ b/net/base/dnsrr_resolver.h @@ -0,0 +1,140 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_BASE_DNSRR_RESOLVER_H_ +#define NET_BASE_DNSRR_RESOLVER_H_ +#pragma once + +#include <map> +#include <string> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/threading/non_thread_safe.h" +#include "base/time.h" +#include "build/build_config.h" +#include "net/base/completion_callback.h" +#include "net/base/net_export.h" +#include "net/base/network_change_notifier.h" + +namespace net { + +// RRResponse contains the result of a successful request for a resource record. +struct NET_EXPORT_PRIVATE RRResponse { + RRResponse(); + ~RRResponse(); + + // HasExpired returns true if |fetch_time| + |ttl| is less than + // |current_time|. + bool HasExpired(base::Time current_time) const; + +#if defined(OS_POSIX) && !defined(OS_ANDROID) + // For testing only + bool ParseFromResponse(const uint8* data, unsigned len, + uint16 rrtype_requested); +#endif + + // name contains the canonical name of the resulting domain. If the queried + // name was a CNAME then this can differ. + std::string name; + // ttl contains the TTL of the resource records. + uint32 ttl; + // dnssec is true if the response was DNSSEC validated. + bool dnssec; + std::vector<std::string> rrdatas; + // sigs contains the RRSIG records returned. + std::vector<std::string> signatures; + // fetch_time is the time at which the response was received from the + // network. + base::Time fetch_time; + // negative is true if this is a negative cache entry, i.e. is a placeholder + // to remember that a given RR doesn't exist. + bool negative; +}; + +class BoundNetLog; +class RRResolverWorker; +class RRResolverJob; + +// DnsRRResolver resolves arbitary DNS resource record types. It should not be +// confused with HostResolver and should not be used to resolve A/AAAA records. +// +// HostResolver exists to lookup addresses and there are many details about +// address resolution over and above DNS (i.e. Bonjour, VPNs etc). +// +// DnsRRResolver should only be used when the data is specifically DNS data and +// the name is a fully qualified DNS domain. +// +// A DnsRRResolver must be used from the MessageLoop which created it. +class NET_EXPORT DnsRRResolver + : NON_EXPORTED_BASE(public base::NonThreadSafe), + public NetworkChangeNotifier::IPAddressObserver { + public: + typedef intptr_t Handle; + + enum { + kInvalidHandle = 0, + }; + + enum { + // Try harder to get a DNSSEC signed response. This doesn't mean that the + // RRResponse will always have the dnssec bit set. + FLAG_WANT_DNSSEC = 1, + }; + + DnsRRResolver(); + virtual ~DnsRRResolver(); + + uint64 requests() const { return requests_; } + uint64 cache_hits() const { return cache_hits_; } + uint64 inflight_joins() const { return inflight_joins_; } + + // Resolve starts the resolution process. When complete, |callback| is called + // with a result. If the result is |OK| then |response| is filled with the + // result of the resolution. Note that |callback| is called via the current + // MessageLoop. + // + // This returns a handle value which can be passed to |CancelResolve|. If + // this function returns kInvalidHandle then the resolution failed + // immediately because it was improperly formed. + Handle Resolve(const std::string& name, uint16 rrtype, + uint16 flags, const CompletionCallback& callback, + RRResponse* response, int priority, + const BoundNetLog& netlog); + + // CancelResolve cancels an inflight lookup. The callback for this lookup + // must not have already been called. + void CancelResolve(Handle handle); + + // Implementation of NetworkChangeNotifier::IPAddressObserver + virtual void OnIPAddressChanged() OVERRIDE; + + private: + friend class RRResolverWorker; + + void HandleResult(const std::string& name, uint16 rrtype, int result, + const RRResponse& response); + + // cache_ maps from a request to a cached response. The cached answer may + // have expired and the size of |cache_| must be <= kMaxCacheEntries. + // < name , rrtype> + std::map<std::pair<std::string, uint16>, RRResponse> cache_; + // inflight_ maps from a request to an active resolution which is taking + // place. + std::map<std::pair<std::string, uint16>, RRResolverJob*> inflight_; + + uint64 requests_; + uint64 cache_hits_; + uint64 inflight_joins_; + + bool in_destructor_; + + DISALLOW_COPY_AND_ASSIGN(DnsRRResolver); +}; + +} // namespace net + +#endif // NET_BASE_DNSRR_RESOLVER_H_ diff --git a/net/base/dnsrr_resolver_unittest.cc b/net/base/dnsrr_resolver_unittest.cc new file mode 100644 index 0000000..cc6e80b --- /dev/null +++ b/net/base/dnsrr_resolver_unittest.cc @@ -0,0 +1,211 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/base/dnsrr_resolver.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/synchronization/lock.h" +#include "net/base/dns_util.h" +#include "net/base/net_errors.h" +#include "net/base/net_log.h" +#include "net/base/test_completion_callback.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +void FailTest(int /* result */) { + FAIL(); +} + +// These tests are disabled because they depend on the external network to +// pass. However, they may be useful when chaging the code. +TEST(DnsRRResolverTest, DISABLED_ResolveReal) { + RRResponse response; + TestCompletionCallback callback; + DnsRRResolver resolver; + DnsRRResolver::Handle handle; + + handle = resolver.Resolve("test.imperialviolet.org", 13172, 0, + callback.callback(), &response, 0, BoundNetLog()); + ASSERT_TRUE(handle != DnsRRResolver::kInvalidHandle); + ASSERT_EQ(OK, callback.WaitForResult()); + ASSERT_EQ(1u, response.rrdatas.size()); + LOG(ERROR) << "result length " << response.rrdatas[0].size(); + LOG(ERROR) << "result is " << response.rrdatas[0]; +} + +TEST(DnsRRResolverTest, DISABLED_ResolveReal2) { + RRResponse response; + TestCompletionCallback callback; + DnsRRResolver resolver; + DnsRRResolver::Handle handle; + + handle = resolver.Resolve("google.com", kDNS_TXT, 0, + callback.callback(), &response, 0, BoundNetLog()); + ASSERT_TRUE(handle != DnsRRResolver::kInvalidHandle); + ASSERT_EQ(OK, callback.WaitForResult()); + ASSERT_EQ(1u, response.rrdatas.size()); + LOG(ERROR) << "result length " << response.rrdatas[0].size(); + LOG(ERROR) << "result is " << response.rrdatas[0]; +} + + +TEST(DnsRRResolverTest, Resolve) { + RRResponse response; + TestCompletionCallback callback; + DnsRRResolver resolver; + DnsRRResolver::Handle handle; + + handle = resolver.Resolve("www.testing.notatld", kDNS_TESTING, 0, + callback.callback(), &response, 0, BoundNetLog()); + ASSERT_TRUE(handle != DnsRRResolver::kInvalidHandle); + ASSERT_EQ(OK, callback.WaitForResult()); + ASSERT_EQ(1u, response.rrdatas.size()); + ASSERT_STREQ("goats!", response.rrdatas[0].c_str()); + ASSERT_EQ(1u, resolver.requests()); + ASSERT_EQ(0u, resolver.cache_hits()); + ASSERT_EQ(0u, resolver.inflight_joins()); + + // Test a cache hit. + handle = resolver.Resolve("www.testing.notatld", kDNS_TESTING, 0, + callback.callback(), &response, 0, BoundNetLog()); + ASSERT_TRUE(handle != DnsRRResolver::kInvalidHandle); + ASSERT_EQ(OK, callback.WaitForResult()); + ASSERT_EQ(1u, response.rrdatas.size()); + ASSERT_STREQ("goats!", response.rrdatas[0].c_str()); + ASSERT_EQ(2u, resolver.requests()); + ASSERT_EQ(1u, resolver.cache_hits()); + ASSERT_EQ(0u, resolver.inflight_joins()); + + // Test that a callback is never made. This depends on there being another + // test after this one which will pump the MessageLoop. + handle = resolver.Resolve("www.testing.notatld", kDNS_TESTING, 0, + base::Bind(&FailTest), &response, 0, BoundNetLog()); + ASSERT_TRUE(handle != DnsRRResolver::kInvalidHandle); + resolver.CancelResolve(handle); + ASSERT_EQ(3u, resolver.requests()); + ASSERT_EQ(2u, resolver.cache_hits()); + ASSERT_EQ(0u, resolver.inflight_joins()); + + // Test what happens in the event of a network config change. + handle = resolver.Resolve("nx.testing.notatld", kDNS_TESTING, 0, + callback.callback(), &response, 0, BoundNetLog()); + ASSERT_TRUE(handle != DnsRRResolver::kInvalidHandle); + resolver.OnIPAddressChanged(); + ASSERT_TRUE(callback.have_result()); + ASSERT_EQ(ERR_ABORTED, callback.WaitForResult()); + ASSERT_EQ(4u, resolver.requests()); + ASSERT_EQ(2u, resolver.cache_hits()); + ASSERT_EQ(0u, resolver.inflight_joins()); + + // Test an inflight join. (Note that this depends on the cache being flushed + // by OnIPAddressChanged.) + TestCompletionCallback callback2; + DnsRRResolver::Handle handle2; + handle = resolver.Resolve("nx.testing.notatld", kDNS_TESTING, 0, + callback.callback(), &response, 0, BoundNetLog()); + ASSERT_TRUE(handle != DnsRRResolver::kInvalidHandle); + handle2 = resolver.Resolve("nx.testing.notatld", kDNS_TESTING, 0, + callback2.callback(), &response, 0, BoundNetLog()); + ASSERT_TRUE(handle2 != DnsRRResolver::kInvalidHandle); + ASSERT_EQ(ERR_NAME_NOT_RESOLVED, callback.WaitForResult()); + ASSERT_EQ(ERR_NAME_NOT_RESOLVED, callback2.WaitForResult()); + ASSERT_EQ(6u, resolver.requests()); + ASSERT_EQ(2u, resolver.cache_hits()); + ASSERT_EQ(1u, resolver.inflight_joins()); +} + +#if defined(OS_POSIX) && !defined(OS_ANDROID) +// This is a DNS packet resulting from querying a recursive resolver for a TXT +// record for agl._pka.imperialviolet.org. You should be able to get a +// replacement from a packet capture should it ever be needed. +static const uint8 kExamplePacket[] = { + 0xce, 0xfe, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x06, 0x00, 0x01, 0x03, + 0x61, 0x67, 0x6c, 0x04, 0x5f, 0x70, 0x6b, 0x61, 0x0e, 0x69, 0x6d, 0x70, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x76, 0x69, 0x6f, 0x6c, 0x65, 0x74, 0x03, 0x6f, 0x72, + 0x67, 0x00, 0x00, 0x10, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x10, 0x00, 0x01, 0x00, + 0x00, 0x01, 0x2c, 0x00, 0x5e, 0x5d, 0x76, 0x3d, 0x70, 0x6b, 0x61, 0x31, 0x3b, + 0x66, 0x70, 0x72, 0x3d, 0x32, 0x41, 0x46, 0x30, 0x30, 0x33, 0x32, 0x42, 0x34, + 0x38, 0x45, 0x38, 0x35, 0x36, 0x43, 0x45, 0x30, 0x36, 0x31, 0x35, 0x37, 0x41, + 0x31, 0x41, 0x44, 0x34, 0x33, 0x43, 0x36, 0x37, 0x30, 0x44, 0x45, 0x30, 0x34, + 0x41, 0x41, 0x41, 0x37, 0x34, 0x3b, 0x75, 0x72, 0x69, 0x3d, 0x68, 0x74, 0x74, + 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x6d, 0x70, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x76, 0x69, 0x6f, 0x6c, 0x65, 0x74, 0x2e, 0x6f, 0x72, 0x67, + 0x2f, 0x6b, 0x65, 0x79, 0x2e, 0x61, 0x73, 0x63, 0xc0, 0x0c, 0x00, 0x2e, 0x00, + 0x01, 0x00, 0x00, 0x01, 0x2c, 0x00, 0xc6, 0x00, 0x10, 0x05, 0x04, 0x00, 0x01, + 0x51, 0x80, 0x4c, 0x74, 0x2f, 0x1a, 0x4c, 0x4c, 0x9c, 0xeb, 0x45, 0xc9, 0x0e, + 0x69, 0x6d, 0x70, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x76, 0x69, 0x6f, 0x6c, 0x65, + 0x74, 0x03, 0x6f, 0x72, 0x67, 0x00, 0x3b, 0x6d, 0x3d, 0xbb, 0xae, 0x1b, 0x07, + 0x8d, 0xa9, 0xb0, 0xa7, 0xa5, 0x7a, 0x84, 0x24, 0x34, 0x29, 0x43, 0x36, 0x3f, + 0x5a, 0x48, 0x3b, 0x79, 0xa3, 0x16, 0xa4, 0x28, 0x5b, 0xd7, 0x03, 0xc6, 0x93, + 0xba, 0x4e, 0x93, 0x4d, 0x18, 0x5c, 0x98, 0xc2, 0x0d, 0x57, 0xd2, 0x6b, 0x9a, + 0x72, 0xbd, 0xe5, 0x8d, 0x10, 0x7b, 0x03, 0xe7, 0x19, 0x1e, 0x51, 0xe5, 0x7e, + 0x49, 0x6b, 0xa3, 0xa8, 0xf1, 0xd3, 0x1b, 0xff, 0x40, 0x26, 0x82, 0x65, 0xd0, + 0x74, 0x8e, 0xcf, 0xc9, 0x71, 0xea, 0x91, 0x57, 0x7e, 0x50, 0x61, 0x4d, 0x4b, + 0x77, 0x05, 0x6a, 0xd8, 0x3f, 0x12, 0x87, 0x50, 0xc2, 0x35, 0x13, 0xab, 0x01, + 0x78, 0xd2, 0x3a, 0x55, 0xa2, 0x89, 0xc8, 0x87, 0xe2, 0x7b, 0xec, 0x51, 0x7c, + 0xc0, 0x24, 0xb5, 0xa3, 0x33, 0x78, 0x98, 0x28, 0x8e, 0x9b, 0x6b, 0x88, 0x13, + 0x25, 0xfa, 0x1d, 0xdc, 0xf1, 0xf0, 0xa6, 0x8d, 0x2a, 0xbb, 0xbc, 0xb0, 0xc7, + 0x97, 0x98, 0x8e, 0xef, 0xd9, 0x12, 0x24, 0xee, 0x38, 0x50, 0xdb, 0xd3, 0x59, + 0xcc, 0x30, 0x54, 0x4c, 0x38, 0x94, 0x24, 0xbc, 0x75, 0xa5, 0xc0, 0xc4, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x15, 0x02, 0x62, 0x30, 0x03, + 0x6f, 0x72, 0x67, 0x0b, 0x61, 0x66, 0x69, 0x6c, 0x69, 0x61, 0x73, 0x2d, 0x6e, + 0x73, 0x74, 0xc0, 0xc4, 0xc0, 0xc4, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x3a, 0x00, 0x19, 0x02, 0x63, 0x30, 0x03, 0x6f, 0x72, 0x67, 0x0b, 0x61, 0x66, + 0x69, 0x6c, 0x69, 0x61, 0x73, 0x2d, 0x6e, 0x73, 0x74, 0x04, 0x69, 0x6e, 0x66, + 0x6f, 0x00, 0xc0, 0xc4, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3a, 0x00, + 0x05, 0x02, 0x61, 0x30, 0xc1, 0x99, 0xc0, 0xc4, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x3a, 0x00, 0x05, 0x02, 0x62, 0x32, 0xc1, 0x78, 0xc0, 0xc4, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x05, 0x02, 0x64, 0x30, 0xc1, + 0x78, 0xc0, 0xc4, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x05, + 0x02, 0x61, 0x32, 0xc1, 0x99, 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, +}; + +TEST(DnsRRResolverTest, ParseExample) { + RRResponse response; + ASSERT_TRUE(response.ParseFromResponse(kExamplePacket, + sizeof(kExamplePacket), kDNS_TXT)); + ASSERT_EQ(1u, response.rrdatas.size()); + ASSERT_EQ(1u, response.signatures.size()); + ASSERT_STREQ("agl._pka.imperialviolet.org", response.name.c_str()); + ASSERT_STREQ("]v=pka1;fpr=2AF0032B48E856CE06157A1AD43C670DE04AAA74;" + "uri=http://www.imperialviolet.org/key.asc", + response.rrdatas[0].c_str()); + ASSERT_FALSE(response.dnssec); +} + +TEST(DnsRRResolverTest, FuzzTruncation) { + RRResponse response; + + for (unsigned len = sizeof(kExamplePacket); len <= sizeof(kExamplePacket); + len--) { + response.ParseFromResponse(kExamplePacket, len, kDNS_TXT); + } +} + +TEST(DnsRRResolverTest, FuzzCorruption) { + RRResponse response; + uint8 copy[sizeof(kExamplePacket)]; + + + for (unsigned bit_to_corrupt = 0; bit_to_corrupt < sizeof(kExamplePacket) * 8; + bit_to_corrupt++) { + unsigned byte = bit_to_corrupt >> 3; + unsigned bit = bit_to_corrupt & 7; + + memcpy(copy, kExamplePacket, sizeof(copy)); + copy[byte] ^= (1 << bit); + + response.ParseFromResponse(copy, sizeof(copy), kDNS_TXT); + } +} +#endif + +} // namespace + +} // namespace net diff --git a/net/net.gyp b/net/net.gyp index e3bdb7a..b7b36be 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -97,6 +97,8 @@ 'base/dns_reloader.h', 'base/dns_util.cc', 'base/dns_util.h', + 'base/dnsrr_resolver.cc', + 'base/dnsrr_resolver.h', 'base/dnssec_chain_verifier.cc', 'base/dnssec_chain_verifier.h', 'base/dnssec_keyset.cc', @@ -1016,6 +1018,7 @@ 'base/directory_lister_unittest.cc', 'base/dnssec_unittest.cc', 'base/dns_util_unittest.cc', + 'base/dnsrr_resolver_unittest.cc', 'base/escape_unittest.cc', 'base/file_stream_unittest.cc', 'base/filter_unittest.cc', |