diff options
author | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-04 15:54:40 +0000 |
---|---|---|
committer | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-04 15:54:40 +0000 |
commit | b3ccac85bee8a3d89c7b5ca3a64ee311f3f565c3 (patch) | |
tree | 6a156892643928ceb04c3c6fe33243c2c9946bee /net/base/dnsrr_resolver.cc | |
parent | 973ce28247510567d996b91ad5aa3f1da590fbcc (diff) | |
download | chromium_src-b3ccac85bee8a3d89c7b5ca3a64ee311f3f565c3.zip chromium_src-b3ccac85bee8a3d89c7b5ca3a64ee311f3f565c3.tar.gz chromium_src-b3ccac85bee8a3d89c7b5ca3a64ee311f3f565c3.tar.bz2 |
net: add DnsRRResovler to fetch arbitary DNS resource types.
(Linux/Mac only for now.)
TEST=net_unittests
BUG=none
http://codereview.chromium.org/3029035
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@54907 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/base/dnsrr_resolver.cc')
-rw-r--r-- | net/base/dnsrr_resolver.cc | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/net/base/dnsrr_resolver.cc b/net/base/dnsrr_resolver.cc new file mode 100644 index 0000000..7a872dd --- /dev/null +++ b/net/base/dnsrr_resolver.cc @@ -0,0 +1,345 @@ +// Copyright (c) 2010 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 <resolv.h> +#endif + +#include "base/string_piece.h" +#include "base/task.h" +#include "base/worker_pool.h" +#include "net/base/dns_reload_timer.h" +#include "net/base/dns_util.h" +#include "net/base/net_errors.h" + +namespace net { + +static const uint16 kClassIN = 1; + +#if defined(OS_POSIX) + +// A Buffer is used for walking over a DNS packet. +class Buffer { + public: + Buffer(const uint8* p, unsigned len) + : p_(p), + packet_(p), + len_(len), + packet_len_(len) { + } + + bool U8(uint8* v) { + if (len_ < 1) + return false; + *v = *p_; + p_++; + len_--; + return true; + } + + bool 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 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 Skip(unsigned n) { + if (len_ < n) + return false; + p_ += n; + len_ -= n; + return true; + } + + bool 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 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]; + } 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; + } + + private: + const uint8* p_; + const uint8* const packet_; + unsigned len_; + const unsigned packet_len_; +}; + +bool DnsRRResolver::Response::ParseFromResponse(const uint8* p, unsigned len, + uint16 rrtype_requested) { + name.clear(); + ttl = 0; + dnssec = false; + rrdatas.clear(); + signatures.clear(); + + // RFC 1035 section 4.4.1 + uint8 flags2; + Buffer 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; +} + +class ResolveTask : public Task { + public: + ResolveTask(const std::string& name, uint16 rrtype, + uint16 flags, CompletionCallback* callback, + DnsRRResolver::Response* response) + : name_(name), + rrtype_(rrtype), + flags_(flags), + callback_(callback), + response_(response) { + } + + virtual void Run() { + // Runs on a worker thread. + + if ((_res.options & RES_INIT) == 0) { + if (res_ninit(&_res) != 0) + return Failure(); + } + + unsigned long saved_options = _res.options; + bool r = Do(); + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD) + if (!r && DnsReloadTimerHasExpired()) { + res_nclose(&_res); + if (res_ninit(&_res) == 0) + r = Do(); + } +#endif + _res.options = saved_options; + int error = r ? OK : ERR_NAME_NOT_RESOLVED; + callback_->Run(error); + } + + 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 + + // 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_); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ResolveTask); + + void Failure() { + callback_->Run(ERR_NAME_NOT_RESOLVED); + } + + const std::string name_; + const uint16 rrtype_; + const uint16 flags_; + CompletionCallback* const callback_; + DnsRRResolver::Response* const response_; +}; +#else // OS_POSIX +// On non-Linux platforms we fail everything for now. +class ResolveTask : public Task { + public: + ResolveTask(const std::string& name, uint16 rrtype, + uint16 flags, CompletionCallback* callback, + DnsRRResolver::Response* response) + : callback_(callback) { + } + + virtual void Run() { + callback_->Run(ERR_NAME_NOT_RESOLVED); + } + + private: + CompletionCallback* const callback_; + DISALLOW_COPY_AND_ASSIGN(ResolveTask); +}; +#endif + +// static +bool DnsRRResolver::Resolve(const std::string& name, uint16 rrtype, + uint16 flags, CompletionCallback* callback, + Response* response) { + if (!callback || !response || name.empty()) + return false; + + // Don't allow queries of type ANY + if (rrtype == kDNS_ANY) + return false; + + ResolveTask* task = new ResolveTask(name, rrtype, flags, callback, response); + + return WorkerPool::PostTask(FROM_HERE, task, true /* task is slow */); +} + +} // namespace net |