1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
8 #include "base/at_exit.h"
9 #include "base/basictypes.h"
10 #include "base/bind.h"
11 #include "base/cancelable_callback.h"
12 #include "base/command_line.h"
13 #include "base/files/file_util.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_split.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/time/time.h"
22 #include "net/base/address_list.h"
23 #include "net/base/ip_endpoint.h"
24 #include "net/base/net_errors.h"
25 #include "net/base/net_util.h"
26 #include "net/dns/dns_client.h"
27 #include "net/dns/dns_config_service.h"
28 #include "net/dns/dns_protocol.h"
29 #include "net/dns/host_cache.h"
30 #include "net/dns/host_resolver_impl.h"
31 #include "net/log/net_log.h"
32 #include "net/tools/gdig/file_net_log.h"
34 #if defined(OS_MACOSX)
35 #include "base/mac/scoped_nsautorelease_pool.h"
42 bool StringToIPEndPoint(const std::string
& ip_address_and_port
,
43 IPEndPoint
* ip_end_point
) {
48 if (!ParseHostAndPort(ip_address_and_port
, &ip
, &port
))
51 port
= dns_protocol::kDefaultPort
;
53 net::IPAddressNumber ip_number
;
54 if (!net::ParseIPLiteralToNumber(ip
, &ip_number
))
57 *ip_end_point
= net::IPEndPoint(ip_number
, static_cast<uint16
>(port
));
61 // Convert DnsConfig to human readable text omitting the hosts member.
62 std::string
DnsConfigToString(const DnsConfig
& dns_config
) {
64 output
.append("search ");
65 for (size_t i
= 0; i
< dns_config
.search
.size(); ++i
) {
66 output
.append(dns_config
.search
[i
] + " ");
70 for (size_t i
= 0; i
< dns_config
.nameservers
.size(); ++i
) {
71 output
.append("nameserver ");
72 output
.append(dns_config
.nameservers
[i
].ToString()).append("\n");
75 base::StringAppendF(&output
, "options ndots:%d\n", dns_config
.ndots
);
76 base::StringAppendF(&output
, "options timeout:%d\n",
77 static_cast<int>(dns_config
.timeout
.InMilliseconds()));
78 base::StringAppendF(&output
, "options attempts:%d\n", dns_config
.attempts
);
79 if (dns_config
.rotate
)
80 output
.append("options rotate\n");
82 output
.append("options edns0\n");
86 // Convert DnsConfig hosts member to a human readable text.
87 std::string
DnsHostsToString(const DnsHosts
& dns_hosts
) {
89 for (DnsHosts::const_iterator i
= dns_hosts
.begin();
92 const DnsHostsKey
& key
= i
->first
;
93 std::string host_name
= key
.first
;
94 output
.append(IPEndPoint(i
->second
, 0).ToStringWithoutPort());
95 output
.append(" ").append(host_name
).append("\n");
100 struct ReplayLogEntry
{
101 base::TimeDelta start_time
;
102 std::string domain_name
;
105 typedef std::vector
<ReplayLogEntry
> ReplayLog
;
107 // Loads and parses a replay log file and fills |replay_log| with a structured
108 // representation. Returns whether the operation was successful. If not, the
109 // contents of |replay_log| are undefined.
111 // The replay log is a text file where each line contains
113 // timestamp_in_milliseconds domain_name
115 // The timestamp_in_milliseconds needs to be an integral delta from start of
116 // resolution and is in milliseconds. domain_name is the name to be resolved.
118 // The file should be sorted by timestamp in ascending time.
119 bool LoadReplayLog(const base::FilePath
& file_path
, ReplayLog
* replay_log
) {
120 std::string original_replay_log_contents
;
121 if (!base::ReadFileToString(file_path
, &original_replay_log_contents
)) {
122 fprintf(stderr
, "Unable to open replay file %s\n",
123 file_path
.MaybeAsASCII().c_str());
127 // Strip out \r characters for Windows files. This isn't as efficient as a
128 // smarter line splitter, but this particular use does not need to target
130 std::string replay_log_contents
;
131 base::RemoveChars(original_replay_log_contents
, "\r", &replay_log_contents
);
133 std::vector
<std::string
> lines
;
134 base::SplitString(replay_log_contents
, '\n', &lines
);
135 base::TimeDelta previous_delta
;
136 bool bad_parse
= false;
137 for (unsigned i
= 0; i
< lines
.size(); ++i
) {
138 if (lines
[i
].empty())
140 std::vector
<std::string
> time_and_name
;
141 base::SplitString(lines
[i
], ' ', &time_and_name
);
142 if (time_and_name
.size() != 2) {
145 "[%s %u] replay log should have format 'timestamp domain_name\\n'\n",
146 file_path
.MaybeAsASCII().c_str(),
152 int64 delta_in_milliseconds
;
153 if (!base::StringToInt64(time_and_name
[0], &delta_in_milliseconds
)) {
156 "[%s %u] replay log should have format 'timestamp domain_name\\n'\n",
157 file_path
.MaybeAsASCII().c_str(),
163 base::TimeDelta delta
=
164 base::TimeDelta::FromMilliseconds(delta_in_milliseconds
);
165 if (delta
< previous_delta
) {
168 "[%s %u] replay log should be sorted by time\n",
169 file_path
.MaybeAsASCII().c_str(),
175 previous_delta
= delta
;
176 ReplayLogEntry entry
;
177 entry
.start_time
= delta
;
178 entry
.domain_name
= time_and_name
[1];
179 replay_log
->push_back(entry
);
190 RESULT_NO_RESOLVE
= -3,
191 RESULT_NO_CONFIG
= -2,
192 RESULT_WRONG_USAGE
= -1,
197 Result
Main(int argc
, const char* argv
[]);
200 bool ParseCommandLine(int argc
, const char* argv
[]);
205 void OnDnsConfig(const DnsConfig
& dns_config_const
);
206 void OnResolveComplete(unsigned index
, AddressList
* address_list
,
207 base::TimeDelta time_since_start
, int val
);
209 void ReplayNextEntry();
211 base::TimeDelta config_timeout_
;
214 net::IPEndPoint nameserver_
;
215 base::TimeDelta timeout_
;
217 ReplayLog replay_log_
;
218 unsigned replay_log_index_
;
219 base::Time start_time_
;
220 int active_resolves_
;
223 base::CancelableClosure timeout_closure_
;
224 scoped_ptr
<DnsConfigService
> dns_config_service_
;
225 scoped_ptr
<FileNetLogObserver
> log_observer_
;
226 scoped_ptr
<NetLog
> log_
;
227 scoped_ptr
<HostResolver
> resolver_
;
229 #if defined(OS_MACOSX)
230 // Without this there will be a mem leak on osx.
231 base::mac::ScopedNSAutoreleasePool scoped_pool_
;
234 // Need AtExitManager to support AsWeakPtr (in NetLog).
235 base::AtExitManager exit_manager_
;
239 : config_timeout_(base::TimeDelta::FromSeconds(5)),
240 print_config_(false),
243 replay_log_index_(0u),
244 active_resolves_(0) {
249 log_
->DeprecatedRemoveObserver(log_observer_
.get());
252 GDig::Result
GDig::Main(int argc
, const char* argv
[]) {
253 if (!ParseCommandLine(argc
, argv
)) {
255 "usage: %s [--net_log[=<basic|no_bytes|all>]]"
256 " [--print_config] [--print_hosts]"
257 " [--nameserver=<ip_address[:port]>]"
258 " [--timeout=<milliseconds>]"
259 " [--config_timeout=<seconds>]"
260 " [--j=<parallel resolves>]"
261 " [--replay_file=<path>]"
264 return RESULT_WRONG_USAGE
;
267 base::MessageLoopForIO loop
;
269 result_
= RESULT_PENDING
;
271 if (result_
== RESULT_PENDING
)
272 base::MessageLoop::current()->Run();
274 // Destroy it while MessageLoopForIO is alive.
275 dns_config_service_
.reset();
279 bool GDig::ParseCommandLine(int argc
, const char* argv
[]) {
280 base::CommandLine::Init(argc
, argv
);
281 const base::CommandLine
& parsed_command_line
=
282 *base::CommandLine::ForCurrentProcess();
284 if (parsed_command_line
.HasSwitch("config_timeout")) {
285 int timeout_seconds
= 0;
286 bool parsed
= base::StringToInt(
287 parsed_command_line
.GetSwitchValueASCII("config_timeout"),
289 if (parsed
&& timeout_seconds
> 0) {
290 config_timeout_
= base::TimeDelta::FromSeconds(timeout_seconds
);
292 fprintf(stderr
, "Invalid config_timeout parameter\n");
297 if (parsed_command_line
.HasSwitch("net_log")) {
298 std::string log_param
= parsed_command_line
.GetSwitchValueASCII("net_log");
299 NetLog::LogLevel level
= NetLog::LOG_ALL_BUT_BYTES
;
301 if (log_param
.length() > 0) {
302 std::map
<std::string
, NetLog::LogLevel
> log_levels
;
303 log_levels
["all"] = NetLog::LOG_ALL
;
304 log_levels
["no_bytes"] = NetLog::LOG_ALL_BUT_BYTES
;
306 if (log_levels
.find(log_param
) != log_levels
.end()) {
307 level
= log_levels
[log_param
];
309 fprintf(stderr
, "Invalid net_log parameter\n");
313 log_
.reset(new NetLog
);
314 log_observer_
.reset(new FileNetLogObserver(stderr
));
315 log_
->DeprecatedAddObserver(log_observer_
.get(), level
);
318 print_config_
= parsed_command_line
.HasSwitch("print_config");
319 print_hosts_
= parsed_command_line
.HasSwitch("print_hosts");
321 if (parsed_command_line
.HasSwitch("nameserver")) {
322 std::string nameserver
=
323 parsed_command_line
.GetSwitchValueASCII("nameserver");
324 if (!StringToIPEndPoint(nameserver
, &nameserver_
)) {
326 "Cannot parse the namerserver string into an IPEndPoint\n");
331 if (parsed_command_line
.HasSwitch("timeout")) {
332 int timeout_millis
= 0;
333 bool parsed
= base::StringToInt(
334 parsed_command_line
.GetSwitchValueASCII("timeout"),
336 if (parsed
&& timeout_millis
> 0) {
337 timeout_
= base::TimeDelta::FromMilliseconds(timeout_millis
);
339 fprintf(stderr
, "Invalid timeout parameter\n");
344 if (parsed_command_line
.HasSwitch("replay_file")) {
345 base::FilePath replay_path
=
346 parsed_command_line
.GetSwitchValuePath("replay_file");
347 if (!LoadReplayLog(replay_path
, &replay_log_
))
351 if (parsed_command_line
.HasSwitch("j")) {
352 int parallellism
= 0;
353 bool parsed
= base::StringToInt(
354 parsed_command_line
.GetSwitchValueASCII("j"),
356 if (parsed
&& parallellism
> 0) {
357 parallellism_
= parallellism
;
359 fprintf(stderr
, "Invalid parallellism parameter\n");
363 if (parsed_command_line
.GetArgs().size() == 1) {
364 ReplayLogEntry entry
;
365 entry
.start_time
= base::TimeDelta();
367 entry
.domain_name
= base::UTF16ToASCII(parsed_command_line
.GetArgs()[0]);
369 entry
.domain_name
= parsed_command_line
.GetArgs()[0];
371 replay_log_
.push_back(entry
);
372 } else if (parsed_command_line
.GetArgs().size() != 0) {
375 return print_config_
|| print_hosts_
|| !replay_log_
.empty();
379 if (nameserver_
.address().size() > 0) {
380 DnsConfig dns_config
;
381 dns_config
.attempts
= 1;
382 dns_config
.nameservers
.push_back(nameserver_
);
383 OnDnsConfig(dns_config
);
385 dns_config_service_
= DnsConfigService::CreateSystemService();
386 dns_config_service_
->ReadConfig(base::Bind(&GDig::OnDnsConfig
,
387 base::Unretained(this)));
388 timeout_closure_
.Reset(base::Bind(&GDig::OnTimeout
,
389 base::Unretained(this)));
390 base::MessageLoop::current()->PostDelayedTask(
391 FROM_HERE
, timeout_closure_
.callback(), config_timeout_
);
395 void GDig::Finish(Result result
) {
396 DCHECK_NE(RESULT_PENDING
, result
);
398 if (base::MessageLoop::current())
399 base::MessageLoop::current()->Quit();
402 void GDig::OnDnsConfig(const DnsConfig
& dns_config_const
) {
403 timeout_closure_
.Cancel();
404 DCHECK(dns_config_const
.IsValid());
405 DnsConfig dns_config
= dns_config_const
;
407 if (timeout_
.InMilliseconds() > 0)
408 dns_config
.timeout
= timeout_
;
410 printf("# Dns Configuration\n"
411 "%s", DnsConfigToString(dns_config
).c_str());
414 printf("# Host Database\n"
415 "%s", DnsHostsToString(dns_config
.hosts
).c_str());
418 if (replay_log_
.empty()) {
423 scoped_ptr
<DnsClient
> dns_client(DnsClient::CreateClient(NULL
));
424 dns_client
->SetConfig(dns_config
);
425 HostResolver::Options options
;
426 options
.max_concurrent_resolves
= parallellism_
;
427 options
.max_retry_attempts
= 1u;
428 scoped_ptr
<HostResolverImpl
> resolver(
429 new HostResolverImpl(options
, log_
.get()));
430 resolver
->SetDnsClient(dns_client
.Pass());
431 resolver_
= resolver
.Pass();
433 start_time_
= base::Time::Now();
438 void GDig::ReplayNextEntry() {
439 DCHECK_LT(replay_log_index_
, replay_log_
.size());
441 base::TimeDelta time_since_start
= base::Time::Now() - start_time_
;
442 while (replay_log_index_
< replay_log_
.size()) {
443 const ReplayLogEntry
& entry
= replay_log_
[replay_log_index_
];
444 if (time_since_start
< entry
.start_time
) {
445 // Delay call to next time and return.
446 base::MessageLoop::current()->PostDelayedTask(
448 base::Bind(&GDig::ReplayNextEntry
, base::Unretained(this)),
449 entry
.start_time
- time_since_start
);
453 HostResolver::RequestInfo
info(HostPortPair(entry
.domain_name
.c_str(), 80));
454 AddressList
* addrlist
= new AddressList();
455 unsigned current_index
= replay_log_index_
;
456 CompletionCallback callback
= base::Bind(&GDig::OnResolveComplete
,
457 base::Unretained(this),
459 base::Owned(addrlist
),
463 int ret
= resolver_
->Resolve(
469 BoundNetLog::Make(log_
.get(), net::NetLog::SOURCE_NONE
));
470 if (ret
!= ERR_IO_PENDING
)
475 void GDig::OnResolveComplete(unsigned entry_index
,
476 AddressList
* address_list
,
477 base::TimeDelta resolve_start_time
,
479 DCHECK_GT(active_resolves_
, 0);
480 DCHECK(address_list
);
481 DCHECK_LT(entry_index
, replay_log_
.size());
483 base::TimeDelta resolve_end_time
= base::Time::Now() - start_time_
;
484 base::TimeDelta resolve_time
= resolve_end_time
- resolve_start_time
;
485 printf("%u %d %d %s %d ",
487 static_cast<int>(resolve_end_time
.InMilliseconds()),
488 static_cast<int>(resolve_time
.InMilliseconds()),
489 replay_log_
[entry_index
].domain_name
.c_str(), val
);
491 std::string error_string
= ErrorToString(val
);
492 printf("%s", error_string
.c_str());
494 for (size_t i
= 0; i
< address_list
->size(); ++i
) {
497 printf("%s", (*address_list
)[i
].ToStringWithoutPort().c_str());
501 if (active_resolves_
== 0 && replay_log_index_
>= replay_log_
.size())
505 void GDig::OnTimeout() {
506 fprintf(stderr
, "Timed out waiting to load the dns config\n");
507 Finish(RESULT_NO_CONFIG
);
514 int main(int argc
, const char* argv
[]) {
516 return dig
.Main(argc
, argv
);