blob: 3fecfe332acdd72348836ed4c418ab7a223289b4 [file] [log] [blame]
// 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