blob: 3385c52c3e479fe296cc3dcc897cd7e8f6c6fce8 [file] [log] [blame]
// 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/dns/dns_config_service_posix.h"
#include <string>
#include "base/compiler_specific.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/memory/scoped_ptr.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_util.h"
#include "net/dns/serial_worker.h"
#include "net/dns/watching_file_reader.h"
#ifndef _PATH_RESCONF // Normally defined in <resolv.h>
#define _PATH_RESCONF "/etc/resolv.conf"
#endif
namespace net {
// A SerialWorker that uses ResolverLib to initialize res_state and converts
// it to DnsConfig.
class DnsConfigServicePosix::ConfigReader : public SerialWorker {
public:
explicit ConfigReader(DnsConfigServicePosix* service)
: service_(service),
success_(false) {}
void DoWork() OVERRIDE {
#if defined(OS_OPENBSD)
if ((res_init() == 0) && (_res.options & RES_INIT)) {
success_ = ConvertResToConfig(_res, &dns_config_);
#else
struct __res_state res;
if ((res_ninit(&res) == 0) && (res.options & RES_INIT)) {
success_ = ConvertResToConfig(res, &dns_config_);
#endif
} else {
// Note: res_ninit in glibc always returns 0 and sets RES_INIT.
// res_init behaves the same way.
success_ = false;
}
#if defined(OS_MACOSX)
res_ndestroy(&res);
#elif !defined(OS_OPENBSD)
res_nclose(&res);
#endif
}
void OnWorkFinished() OVERRIDE {
DCHECK(!IsCancelled());
if (success_)
service_->OnConfigRead(dns_config_);
}
private:
virtual ~ConfigReader() {}
DnsConfigServicePosix* service_;
// Written in DoWork, read in OnWorkFinished, no locking necessary.
DnsConfig dns_config_;
bool success_;
};
DnsConfigServicePosix::DnsConfigServicePosix()
: config_watcher_(new WatchingFileReader()),
hosts_watcher_(new WatchingFileReader()) {}
DnsConfigServicePosix::~DnsConfigServicePosix() {}
void DnsConfigServicePosix::Watch() {
DCHECK(CalledOnValidThread());
config_watcher_->StartWatch(FilePath(FILE_PATH_LITERAL(_PATH_RESCONF)),
new ConfigReader(this));
FilePath hosts_path(FILE_PATH_LITERAL("/etc/hosts"));
hosts_watcher_->StartWatch(hosts_path, new DnsHostsReader(hosts_path, this));
}
// static
DnsConfigService* DnsConfigService::CreateSystemService() {
return new DnsConfigServicePosix();
}
bool ConvertResToConfig(const struct __res_state& res, DnsConfig* dns_config) {
CHECK(dns_config != NULL);
DCHECK(res.options & RES_INIT);
dns_config->nameservers.clear();
#if defined(OS_LINUX)
// Initially, glibc stores IPv6 in _ext.nsaddrs and IPv4 in nsaddr_list.
// Next (res_send.c::__libc_res_nsend), it copies nsaddr_list after nsaddrs.
// If RES_ROTATE is enabled, the list is shifted left after each res_send.
// However, if nsaddr_list changes, it will refill nsaddr_list (IPv4) but
// leave the IPv6 entries in nsaddr in the same (shifted) order.
// Put IPv6 addresses ahead of IPv4.
for (int i = 0; i < res._u._ext.nscount6; ++i) {
IPEndPoint ipe;
if (ipe.FromSockAddr(
reinterpret_cast<const struct sockaddr*>(res._u._ext.nsaddrs[i]),
sizeof *res._u._ext.nsaddrs[i])) {
dns_config->nameservers.push_back(ipe);
} else {
return false;
}
}
#endif
for (int i = 0; i < res.nscount; ++i) {
IPEndPoint ipe;
if (ipe.FromSockAddr(
reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]),
sizeof res.nsaddr_list[i])) {
dns_config->nameservers.push_back(ipe);
} else {
return false;
}
}
dns_config->search.clear();
for (int i = 0; (i < MAXDNSRCH) && res.dnsrch[i]; ++i) {
dns_config->search.push_back(std::string(res.dnsrch[i]));
}
dns_config->ndots = res.ndots;
dns_config->timeout = base::TimeDelta::FromSeconds(res.retrans);
dns_config->attempts = res.retry;
#if defined(RES_ROTATE)
dns_config->rotate = res.options & RES_ROTATE;
#endif
dns_config->edns0 = res.options & RES_USE_EDNS0;
return true;
}
} // namespace net