// 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 <stdio.h>
#include <string>
#include "base/at_exit.h"
#include "base/bind.h"
#include "base/cancelable_callback.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "net/base/address_list.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/base/net_log.h"
#include "net/base/net_util.h"
#include "net/dns/dns_client.h"
#include "net/dns/dns_config_service.h"
#include "net/dns/dns_protocol.h"
#include "net/dns/host_cache.h"
#include "net/dns/host_resolver_impl.h"
#include "net/tools/gdig/file_net_log.h"
#if defined(OS_MACOSX)
#include "base/mac/scoped_nsautorelease_pool.h"
#endif
namespace net {
namespace {
bool StringToIPEndPoint(const std::string& ip_address_and_port,
IPEndPoint* ip_end_point) {
DCHECK(ip_end_point);
std::string ip;
int port;
if (!ParseHostAndPort(ip_address_and_port, &ip, &port))
return false;
if (port == -1)
port = dns_protocol::kDefaultPort;
net::IPAddressNumber ip_number;
if (!net::ParseIPLiteralToNumber(ip, &ip_number))
return false;
*ip_end_point = net::IPEndPoint(ip_number, port);
return true;
}
// Convert DnsConfig to human readable text omitting the hosts member.
std::string DnsConfigToString(const DnsConfig& dns_config) {
std::string output;
output.append("search ");
for (size_t i = 0; i < dns_config.search.size(); ++i) {
output.append(dns_config.search[i] + " ");
}
output.append("\n");
for (size_t i = 0; i < dns_config.nameservers.size(); ++i) {
output.append("nameserver ");
output.append(dns_config.nameservers[i].ToString()).append("\n");
}
base::StringAppendF(&output, "options ndots:%d\n", dns_config.ndots);
base::StringAppendF(&output, "options timeout:%d\n",
static_cast<int>(dns_config.timeout.InMilliseconds()));
base::StringAppendF(&output, "options attempts:%d\n", dns_config.attempts);
if (dns_config.rotate)
output.append("options rotate\n");
if (dns_config.edns0)
output.append("options edns0\n");
return output;
}
// Convert DnsConfig hosts member to a human readable text.
std::string DnsHostsToString(const DnsHosts& dns_hosts) {
std::string output;
for (DnsHosts::const_iterator i = dns_hosts.begin();
i != dns_hosts.end();
++i) {
const DnsHostsKey& key = i->first;
std::string host_name = key.first;
output.append(IPEndPoint(i->second, -1).ToStringWithoutPort());
output.append(" ").append(host_name).append("\n");
}
return output;
}
struct ReplayLogEntry {
base::TimeDelta start_time;
std::string domain_name;
};
typedef std::vector<ReplayLogEntry> ReplayLog;
// Loads and parses a replay log file and fills |replay_log| with a structured
// representation. Returns whether the operation was successful. If not, the
// contents of |replay_log| are undefined.
//
// The replay log is a text file where each line contains
//
// timestamp_in_milliseconds domain_name
//
// The timestamp_in_milliseconds needs to be an integral delta from start of
// resolution and is in milliseconds. domain_name is the name to be resolved.
//
// The file should be sorted by timestamp in ascending time.
bool LoadReplayLog(const base::FilePath& file_path, ReplayLog* replay_log) {
std::string original_replay_log_contents;
if (!base::ReadFileToString(file_path, &original_replay_log_contents)) {
fprintf(stderr, "Unable to open replay file %s\n",
file_path.MaybeAsASCII().c_str());
return false;
}
// Strip out \r characters for Windows files. This isn't as efficient as a
// smarter line splitter, but this particular use does not need to target
// efficiency.
std::string replay_log_contents;
base::RemoveChars(original_replay_log_contents, "\r", &replay_log_contents);
std::vector<std::string> lines;
base::SplitString(replay_log_contents, '\n', &lines);
base::TimeDelta previous_delta;
bool bad_parse = false;
for (unsigned i = 0; i < lines.size(); ++i) {
if (lines[i].empty())
continue;
std::vector<std::string> time_and_name;
base::SplitString(lines[i], ' ', &time_and_name);
if (time_and_name.size() != 2) {
fprintf(
stderr,
"[%s %u] replay log should have format 'timestamp domain_name\\n'\n",
file_path.MaybeAsASCII().c_str(),
i + 1);
bad_parse = true;
continue;
}
int64 delta_in_milliseconds;
if (!base::StringToInt64(time_and_name[0], &delta_in_milliseconds)) {
fprintf(
stderr,
"[%s %u] replay log should have format 'timestamp domain_name\\n'\n",
file_path.MaybeAsASCII().c_str(),
i + 1);
bad_parse = true;
continue;
}
base::TimeDelta delta =
base::TimeDelta::FromMilliseconds(delta_in_milliseconds);
if (delta < previous_delta) {
fprintf(
stderr,
"[%s %u] replay log should be sorted by time\n",
file_path.MaybeAsASCII().c_str(),
i + 1);
bad_parse = true;
continue;
}
previous_delta = delta;
ReplayLogEntry entry;
entry.start_time = delta;
entry.domain_name = time_and_name[1];
replay_log->push_back(entry);
}
return !bad_parse;
}
class GDig {
public:
GDig();
~GDig();
enum Result {
RESULT_NO_RESOLVE = -3,
RESULT_NO_CONFIG = -2,
RESULT_WRONG_USAGE = -1,
RESULT_OK = 0,
RESULT_PENDING = 1,
};
Result Main(int argc, const char* argv[]);
private:
bool ParseCommandLine(int argc, const char* argv[]);
void Start();
void Finish(Result);
void OnDnsConfig(const DnsConfig& dns_config_const);
void OnResolveComplete(unsigned index, AddressList* address_list,
base::TimeDelta time_since_start, int val);
void OnTimeout();
void ReplayNextEntry();
base::TimeDelta config_timeout_;
bool print_config_;
bool print_hosts_;
net::IPEndPoint nameserver_;
base::TimeDelta timeout_;
int parallellism_;
ReplayLog replay_log_;
unsigned replay_log_index_;
base::Time start_time_;
int active_resolves_;
Result result_;
base::CancelableClosure timeout_closure_;
scoped_ptr<DnsConfigService> dns_config_service_;
scoped_ptr<FileNetLogObserver> log_observer_;
scoped_ptr<NetLog> log_;
scoped_ptr<HostResolver> resolver_;
#if defined(OS_MACOSX)
// Without this there will be a mem leak on osx.
base::mac::ScopedNSAutoreleasePool scoped_pool_;
#endif
// Need AtExitManager to support AsWeakPtr (in NetLog).
base::AtExitManager exit_manager_;
};
GDig::GDig()
: config_timeout_(base::TimeDelta::FromSeconds(5)),
print_config_(false),
print_hosts_(false),
parallellism_(6),
replay_log_index_(0u),
active_resolves_(0) {
}
GDig::~GDig() {
if (log_)
log_->RemoveThreadSafeObserver(log_observer_.get());
}
GDig::Result GDig::Main(int argc, const char* argv[]) {
if (!ParseCommandLine(argc, argv)) {
fprintf(stderr,
"usage: %s [--net_log[=<basic|no_bytes|all>]]"
" [--print_config] [--print_hosts]"
" [--nameserver=<ip_address[:port]>]"
" [--timeout=<milliseconds>]"
" [--config_timeout=<seconds>]"
" [--j=<parallel resolves>]"
" [--replay_file=<path>]"
" [domain_name]\n",
argv[0]);
return RESULT_WRONG_USAGE;
}
base::MessageLoopForIO loop;
result_ = RESULT_PENDING;
Start();
if (result_ == RESULT_PENDING)
base::MessageLoop::current()->Run();
// Destroy it while MessageLoopForIO is alive.
dns_config_service_.reset();
return result_;
}
bool GDig::ParseCommandLine(int argc, const char* argv[]) {
base::CommandLine::Init(argc, argv);
const base::CommandLine& parsed_command_line =
*base::CommandLine::ForCurrentProcess();
if (parsed_command_line.HasSwitch("config_timeout")) {
int timeout_seconds = 0;
bool parsed = base::StringToInt(
parsed_command_line.GetSwitchValueASCII("config_timeout"),
&timeout_seconds);
if (parsed && timeout_seconds > 0) {
config_timeout_ = base::TimeDelta::FromSeconds(timeout_seconds);
} else {
fprintf(stderr, "Invalid config_timeout parameter\n");
return false;
}
}
if (parsed_command_line.HasSwitch("net_log")) {
std::string log_param = parsed_command_line.GetSwitchValueASCII("net_log");
NetLog::LogLevel level = NetLog::LOG_ALL_BUT_BYTES;
if (log_param.length() > 0) {
std::map<std::string, NetLog::LogLevel> log_levels;
log_levels["all"] = NetLog::LOG_ALL;
log_levels["no_bytes"] = NetLog::LOG_ALL_BUT_BYTES;
if (log_levels.find(log_param) != log_levels.end()) {
level = log_levels[log_param];
} else {
fprintf(stderr, "Invalid net_log parameter\n");
return false;
}
}
log_.reset(new NetLog);
log_observer_.reset(new FileNetLogObserver(stderr));
log_->AddThreadSafeObserver(log_observer_.get(), level);
}
print_config_ = parsed_command_line.HasSwitch("print_config");
print_hosts_ = parsed_command_line.HasSwitch("print_hosts");
if (parsed_command_line.HasSwitch("nameserver")) {
std::string nameserver =
parsed_command_line.GetSwitchValueASCII("nameserver");
if (!StringToIPEndPoint(nameserver, &nameserver_)) {
fprintf(stderr,
"Cannot parse the namerserver string into an IPEndPoint\n");
return false;
}
}
if (parsed_command_line.HasSwitch("timeout")) {
int timeout_millis = 0;
bool parsed = base::StringToInt(
parsed_command_line.GetSwitchValueASCII("timeout"),
&timeout_millis);
if (parsed && timeout_millis > 0) {
timeout_ = base::TimeDelta::FromMilliseconds(timeout_millis);
} else {
fprintf(stderr, "Invalid timeout parameter\n");
return false;
}
}
if (parsed_command_line.HasSwitch("replay_file")) {
base::FilePath replay_path =
parsed_command_line.GetSwitchValuePath("replay_file");
if (!LoadReplayLog(replay_path, &replay_log_))
return false;
}
if (parsed_command_line.HasSwitch("j")) {
int parallellism = 0;
bool parsed = base::StringToInt(
parsed_command_line.GetSwitchValueASCII("j"),
¶llellism);
if (parsed && parallellism > 0) {
parallellism_ = parallellism;
} else {
fprintf(stderr, "Invalid parallellism parameter\n");
}
}
if (parsed_command_line.GetArgs().size() == 1) {
ReplayLogEntry entry;
entry.start_time = base::TimeDelta();
#if defined(OS_WIN)
entry.domain_name = base::UTF16ToASCII(parsed_command_line.GetArgs()[0]);
#else
entry.domain_name = parsed_command_line.GetArgs()[0];
#endif
replay_log_.push_back(entry);
} else if (parsed_command_line.GetArgs().size() != 0) {
return false;
}
return print_config_ || print_hosts_ || !replay_log_.empty();
}
void GDig::Start() {
if (nameserver_.address().size() > 0) {
DnsConfig dns_config;
dns_config.attempts = 1;
dns_config.nameservers.push_back(nameserver_);
OnDnsConfig(dns_config);
} else {
dns_config_service_ = DnsConfigService::CreateSystemService();
dns_config_service_->ReadConfig(base::Bind(&GDig::OnDnsConfig,
base::Unretained(this)));
timeout_closure_.Reset(base::Bind(&GDig::OnTimeout,
base::Unretained(this)));
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE, timeout_closure_.callback(), config_timeout_);
}
}
void GDig::Finish(Result result) {
DCHECK_NE(RESULT_PENDING, result);
result_ = result;
if (base::MessageLoop::current())
base::MessageLoop::current()->Quit();
}
void GDig::OnDnsConfig(const DnsConfig& dns_config_const) {
timeout_closure_.Cancel();
DCHECK(dns_config_const.IsValid());
DnsConfig dns_config = dns_config_const;
if (timeout_.InMilliseconds() > 0)
dns_config.timeout = timeout_;
if (print_config_) {
printf("# Dns Configuration\n"
"%s", DnsConfigToString(dns_config).c_str());
}
if (print_hosts_) {
printf("# Host Database\n"
"%s", DnsHostsToString(dns_config.hosts).c_str());
}
if (replay_log_.empty()) {
Finish(RESULT_OK);
return;
}
scoped_ptr<DnsClient> dns_client(DnsClient::CreateClient(NULL));
dns_client->SetConfig(dns_config);
HostResolver::Options options;
options.max_concurrent_resolves = parallellism_;
options.max_retry_attempts = 1u;
scoped_ptr<HostResolverImpl> resolver(
new HostResolverImpl(options, log_.get()));
resolver->SetDnsClient(dns_client.Pass());
resolver_ = resolver.Pass();
start_time_ = base::Time::Now();
ReplayNextEntry();
}
void GDig::ReplayNextEntry() {
DCHECK_LT(replay_log_index_, replay_log_.size());
base::TimeDelta time_since_start = base::Time::Now() - start_time_;
while (replay_log_index_ < replay_log_.size()) {
const ReplayLogEntry& entry = replay_log_[replay_log_index_];
if (time_since_start < entry.start_time) {
// Delay call to next time and return.
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&GDig::ReplayNextEntry, base::Unretained(this)),
entry.start_time - time_since_start);
return;
}
HostResolver::RequestInfo info(HostPortPair(entry.domain_name.c_str(), 80));
AddressList* addrlist = new AddressList();
unsigned current_index = replay_log_index_;
CompletionCallback callback = base::Bind(&GDig::OnResolveComplete,
base::Unretained(this),
current_index,
base::Owned(addrlist),
time_since_start);
++active_resolves_;
++replay_log_index_;
int ret = resolver_->Resolve(
info,
DEFAULT_PRIORITY,
addrlist,
callback,
NULL,
BoundNetLog::Make(log_.get(), net::NetLog::SOURCE_NONE));
if (ret != ERR_IO_PENDING)
callback.Run(ret);
}
}
void GDig::OnResolveComplete(unsigned entry_index,
AddressList* address_list,
base::TimeDelta resolve_start_time,
int val) {
DCHECK_GT(active_resolves_, 0);
DCHECK(address_list);
DCHECK_LT(entry_index, replay_log_.size());
--active_resolves_;
base::TimeDelta resolve_end_time = base::Time::Now() - start_time_;
base::TimeDelta resolve_time = resolve_end_time - resolve_start_time;
printf("%u %d %d %s %d ",
entry_index,
static_cast<int>(resolve_end_time.InMilliseconds()),
static_cast<int>(resolve_time.InMilliseconds()),
replay_log_[entry_index].domain_name.c_str(), val);
if (val != OK) {
std::string error_string = ErrorToString(val);
printf("%s", error_string.c_str());
} else {
for (size_t i = 0; i < address_list->size(); ++i) {
if (i != 0)
printf(" ");
printf("%s", (*address_list)[i].ToStringWithoutPort().c_str());
}
}
printf("\n");
if (active_resolves_ == 0 && replay_log_index_ >= replay_log_.size())
Finish(RESULT_OK);
}
void GDig::OnTimeout() {
fprintf(stderr, "Timed out waiting to load the dns config\n");
Finish(RESULT_NO_CONFIG);
}
} // empty namespace
} // namespace net
int main(int argc, const char* argv[]) {
net::GDig dig;
return dig.Main(argc, argv);
}