Chromium Code Reviews| Index: net/base/dnsrr_resolver.cc |
| diff --git a/net/base/dnsrr_resolver.cc b/net/base/dnsrr_resolver.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..9b98f6076dae60934081ea6f0375c6c0a4132300 |
| --- /dev/null |
| +++ b/net/base/dnsrr_resolver.cc |
| @@ -0,0 +1,324 @@ |
| +// 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_LINUX) |
| +#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_LINUX) |
| + |
| +// 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; |
|
Mike Belshe
2010/08/02 14:18:28
nit: I hate all these 1 char variable names!
|
| + p++; |
| + len--; |
| + |
| + if ((d & 0xc0) == 0xc0) { |
|
Mike Belshe
2010/08/02 14:18:28
I'm sure 0xc0 is a mask of some sort; for those no
|
| + if (jumps > 100) |
|
Mike Belshe
2010/08/02 14:18:28
100 is a hardcoded limit for ???
|
| + 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. |
| + if (_res.nscount == 1 && |
| + memcmp(&_res.nsaddr_list[0].sin_addr, |
| + "\x7f\x00\x00\x01" /* 127.0.0.1 */, 4) == 0) { |
|
Mike Belshe
2010/08/02 14:18:28
Is there any ipv6 issue here? Maybe a TODO
|
| + 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 (!r && DnsReloadTimerHasExpired()) { |
| + res_nclose(&_res); |
| + if (res_ninit(&_res) == 0) |
| + r = Do(); |
| + } |
| + _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; |
| + |
| + // 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: |
| + 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_LINUX |
| +// On non-Linux platforms we fail everything for now. |
| +class ResolveTask { |
| + public: |
| + ResolveTask(const std::string& name, uint16 rrtype, |
| + uint16 flags, CompletionCallback* callback, |
| + Response* response) { |
| + } |
| + |
| + virtual void Run() { |
| + callback_->Run(ERR_NAME_NOT_RESOLVED); |
| + } |
| +}; |
| +#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 |