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 |