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"
10 #include "base/cancelable_callback.h"
11 #include "base/command_line.h"
12 #include "base/files/file_util.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_split.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/time/time.h"
21 #include "net/base/address_list.h"
22 #include "net/base/ip_endpoint.h"
23 #include "net/base/net_errors.h"
24 #include "net/base/net_log.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/tools/gdig/file_net_log.h"
33 #if defined(OS_MACOSX)
34 #include "base/mac/scoped_nsautorelease_pool.h"
41 bool StringToIPEndPoint(const std::string
& ip_address_and_port
,
42 IPEndPoint
* ip_end_point
) {
47 if (!ParseHostAndPort(ip_address_and_port
, &ip
, &port
))
50 port
= dns_protocol::kDefaultPort
;
52 net::IPAddressNumber ip_number
;
53 if (!net::ParseIPLiteralToNumber(ip
, &ip_number
))
56 *ip_end_point
= net::IPEndPoint(ip_number
, port
);
60 // Convert DnsConfig to human readable text omitting the hosts member.
61 std::string
DnsConfigToString(const DnsConfig
& dns_config
) {
63 output
.append("search ");
64 for (size_t i
= 0; i
< dns_config
.search
.size(); ++i
) {
65 output
.append(dns_config
.search
[i
] + " ");
69 for (size_t i
= 0; i
< dns_config
.nameservers
.size(); ++i
) {
70 output
.append("nameserver ");
71 output
.append(dns_config
.nameservers
[i
].ToString()).append("\n");
74 base::StringAppendF(&output
, "options ndots:%d\n", dns_config
.ndots
);
75 base::StringAppendF(&output
, "options timeout:%d\n",
76 static_cast<int>(dns_config
.timeout
.InMilliseconds()));
77 base::StringAppendF(&output
, "options attempts:%d\n", dns_config
.attempts
);
78 if (dns_config
.rotate
)
79 output
.append("options rotate\n");
81 output
.append("options edns0\n");
85 // Convert DnsConfig hosts member to a human readable text.
86 std::string
DnsHostsToString(const DnsHosts
& dns_hosts
) {
88 for (DnsHosts::const_iterator i
= dns_hosts
.begin();
91 const DnsHostsKey
& key
= i
->first
;
92 std::string host_name
= key
.first
;
93 output
.append(IPEndPoint(i
->second
, -1).ToStringWithoutPort());
94 output
.append(" ").append(host_name
).append("\n");
99 struct ReplayLogEntry
{
100 base::TimeDelta start_time
;
101 std::string domain_name
;
104 typedef std::vector
<ReplayLogEntry
> ReplayLog
;
106 // Loads and parses a replay log file and fills |replay_log| with a structured
107 // representation. Returns whether the operation was successful. If not, the
108 // contents of |replay_log| are undefined.
110 // The replay log is a text file where each line contains
112 // timestamp_in_milliseconds domain_name
114 // The timestamp_in_milliseconds needs to be an integral delta from start of
115 // resolution and is in milliseconds. domain_name is the name to be resolved.
117 // The file should be sorted by timestamp in ascending time.
118 bool LoadReplayLog(const base::FilePath
& file_path
, ReplayLog
* replay_log
) {
119 std::string original_replay_log_contents
;
120 if (!base::ReadFileToString(file_path
, &original_replay_log_contents
)) {
121 fprintf(stderr
, "Unable to open replay file %s\n",
122 file_path
.MaybeAsASCII().c_str());
126 // Strip out \r characters for Windows files. This isn't as efficient as a
127 // smarter line splitter, but this particular use does not need to target
129 std::string replay_log_contents
;
130 base::RemoveChars(original_replay_log_contents
, "\r", &replay_log_contents
);
132 std::vector
<std::string
> lines
;
133 base::SplitString(replay_log_contents
, '\n', &lines
);
134 base::TimeDelta previous_delta
;
135 bool bad_parse
= false;
136 for (unsigned i
= 0; i
< lines
.size(); ++i
) {
137 if (lines
[i
].empty())
139 std::vector
<std::string
> time_and_name
;
140 base::SplitString(lines
[i
], ' ', &time_and_name
);
141 if (time_and_name
.size() != 2) {
144 "[%s %u] replay log should have format 'timestamp domain_name\\n'\n",
145 file_path
.MaybeAsASCII().c_str(),
151 int64 delta_in_milliseconds
;
152 if (!base::StringToInt64(time_and_name
[0], &delta_in_milliseconds
)) {
155 "[%s %u] replay log should have format 'timestamp domain_name\\n'\n",
156 file_path
.MaybeAsASCII().c_str(),
162 base::TimeDelta delta
=
163 base::TimeDelta::FromMilliseconds(delta_in_milliseconds
);
164 if (delta
< previous_delta
) {
167 "[%s %u] replay log should be sorted by time\n",
168 file_path
.MaybeAsASCII().c_str(),
174 previous_delta
= delta
;
175 ReplayLogEntry entry
;
176 entry
.start_time
= delta
;
177 entry
.domain_name
= time_and_name
[1];
178 replay_log
->push_back(entry
);
189 RESULT_NO_RESOLVE
= -3,
190 RESULT_NO_CONFIG
= -2,
191 RESULT_WRONG_USAGE
= -1,
196 Result
Main(int argc
, const char* argv
[]);
199 bool ParseCommandLine(int argc
, const char* argv
[]);
204 void OnDnsConfig(const DnsConfig
& dns_config_const
);
205 void OnResolveComplete(unsigned index
, AddressList
* address_list
,
206 base::TimeDelta time_since_start
, int val
);
208 void ReplayNextEntry();
210 base::TimeDelta config_timeout_
;
213 net::IPEndPoint nameserver_
;
214 base::TimeDelta timeout_
;
216 ReplayLog replay_log_
;
217 unsigned replay_log_index_
;
218 base::Time start_time_
;
219 int active_resolves_
;
222 base::CancelableClosure timeout_closure_
;
223 scoped_ptr
<DnsConfigService
> dns_config_service_
;
224 scoped_ptr
<FileNetLogObserver
> log_observer_
;
225 scoped_ptr
<NetLog
> log_
;
226 scoped_ptr
<HostResolver
> resolver_
;
228 #if defined(OS_MACOSX)
229 // Without this there will be a mem leak on osx.
230 base::mac::ScopedNSAutoreleasePool scoped_pool_
;
233 // Need AtExitManager to support AsWeakPtr (in NetLog).
234 base::AtExitManager exit_manager_
;
238 : config_timeout_(base::TimeDelta::FromSeconds(5)),
239 print_config_(false),
242 replay_log_index_(0u),
243 active_resolves_(0) {
248 log_
->RemoveThreadSafeObserver(log_observer_
.get());
251 GDig::Result
GDig::Main(int argc
, const char* argv
[]) {
252 if (!ParseCommandLine(argc
, argv
)) {
254 "usage: %s [--net_log[=<basic|no_bytes|all>]]"
255 " [--print_config] [--print_hosts]"
256 " [--nameserver=<ip_address[:port]>]"
257 " [--timeout=<milliseconds>]"
258 " [--config_timeout=<seconds>]"
259 " [--j=<parallel resolves>]"
260 " [--replay_file=<path>]"
263 return RESULT_WRONG_USAGE
;
266 base::MessageLoopForIO loop
;
268 result_
= RESULT_PENDING
;
270 if (result_
== RESULT_PENDING
)
271 base::MessageLoop::current()->Run();
273 // Destroy it while MessageLoopForIO is alive.
274 dns_config_service_
.reset();
278 bool GDig::ParseCommandLine(int argc
, const char* argv
[]) {
279 base::CommandLine::Init(argc
, argv
);
280 const base::CommandLine
& parsed_command_line
=
281 *base::CommandLine::ForCurrentProcess();
283 if (parsed_command_line
.HasSwitch("config_timeout")) {
284 int timeout_seconds
= 0;
285 bool parsed
= base::StringToInt(
286 parsed_command_line
.GetSwitchValueASCII("config_timeout"),
288 if (parsed
&& timeout_seconds
> 0) {
289 config_timeout_
= base::TimeDelta::FromSeconds(timeout_seconds
);
291 fprintf(stderr
, "Invalid config_timeout parameter\n");
296 if (parsed_command_line
.HasSwitch("net_log")) {
297 std::string log_param
= parsed_command_line
.GetSwitchValueASCII("net_log");
298 NetLog::LogLevel level
= NetLog::LOG_ALL_BUT_BYTES
;
300 if (log_param
.length() > 0) {
301 std::map
<std::string
, NetLog::LogLevel
> log_levels
;
302 log_levels
["all"] = NetLog::LOG_ALL
;
303 log_levels
["no_bytes"] = NetLog::LOG_ALL_BUT_BYTES
;
305 if (log_levels
.find(log_param
) != log_levels
.end()) {
306 level
= log_levels
[log_param
];
308 fprintf(stderr
, "Invalid net_log parameter\n");
312 log_
.reset(new NetLog
);
313 log_observer_
.reset(new FileNetLogObserver(stderr
));
314 log_
->AddThreadSafeObserver(log_observer_
.get(), level
);
317 print_config_
= parsed_command_line
.HasSwitch("print_config");
318 print_hosts_
= parsed_command_line
.HasSwitch("print_hosts");
320 if (parsed_command_line
.HasSwitch("nameserver")) {
321 std::string nameserver
=
322 parsed_command_line
.GetSwitchValueASCII("nameserver");
323 if (!StringToIPEndPoint(nameserver
, &nameserver_
)) {
325 "Cannot parse the namerserver string into an IPEndPoint\n");
330 if (parsed_command_line
.HasSwitch("timeout")) {
331 int timeout_millis
= 0;
332 bool parsed
= base::StringToInt(
333 parsed_command_line
.GetSwitchValueASCII("timeout"),
335 if (parsed
&& timeout_millis
> 0) {
336 timeout_
= base::TimeDelta::FromMilliseconds(timeout_millis
);
338 fprintf(stderr
, "Invalid timeout parameter\n");
343 if (parsed_command_line
.HasSwitch("replay_file")) {
344 base::FilePath replay_path
=
345 parsed_command_line
.GetSwitchValuePath("replay_file");
346 if (!LoadReplayLog(replay_path
, &replay_log_
))
350 if (parsed_command_line
.HasSwitch("j")) {
351 int parallellism
= 0;
352 bool parsed
= base::StringToInt(
353 parsed_command_line
.GetSwitchValueASCII("j"),
355 if (parsed
&& parallellism
> 0) {
356 parallellism_
= parallellism
;
358 fprintf(stderr
, "Invalid parallellism parameter\n");
362 if (parsed_command_line
.GetArgs().size() == 1) {
363 ReplayLogEntry entry
;
364 entry
.start_time
= base::TimeDelta();
366 entry
.domain_name
= base::UTF16ToASCII(parsed_command_line
.GetArgs()[0]);
368 entry
.domain_name
= parsed_command_line
.GetArgs()[0];
370 replay_log_
.push_back(entry
);
371 } else if (parsed_command_line
.GetArgs().size() != 0) {
374 return print_config_
|| print_hosts_
|| !replay_log_
.empty();
378 if (nameserver_
.address().size() > 0) {
379 DnsConfig dns_config
;
380 dns_config
.attempts
= 1;
381 dns_config
.nameservers
.push_back(nameserver_
);
382 OnDnsConfig(dns_config
);
384 dns_config_service_
= DnsConfigService::CreateSystemService();
385 dns_config_service_
->ReadConfig(base::Bind(&GDig::OnDnsConfig
,
386 base::Unretained(this)));
387 timeout_closure_
.Reset(base::Bind(&GDig::OnTimeout
,
388 base::Unretained(this)));
389 base::MessageLoop::current()->PostDelayedTask(
390 FROM_HERE
, timeout_closure_
.callback(), config_timeout_
);
394 void GDig::Finish(Result result
) {
395 DCHECK_NE(RESULT_PENDING
, result
);
397 if (base::MessageLoop::current())
398 base::MessageLoop::current()->Quit();
401 void GDig::OnDnsConfig(const DnsConfig
& dns_config_const
) {
402 timeout_closure_
.Cancel();
403 DCHECK(dns_config_const
.IsValid());
404 DnsConfig dns_config
= dns_config_const
;
406 if (timeout_
.InMilliseconds() > 0)
407 dns_config
.timeout
= timeout_
;
409 printf("# Dns Configuration\n"
410 "%s", DnsConfigToString(dns_config
).c_str());
413 printf("# Host Database\n"
414 "%s", DnsHostsToString(dns_config
.hosts
).c_str());
417 if (replay_log_
.empty()) {
422 scoped_ptr
<DnsClient
> dns_client(DnsClient::CreateClient(NULL
));
423 dns_client
->SetConfig(dns_config
);
424 HostResolver::Options options
;
425 options
.max_concurrent_resolves
= parallellism_
;
426 options
.max_retry_attempts
= 1u;
427 scoped_ptr
<HostResolverImpl
> resolver(
428 new HostResolverImpl(options
, log_
.get()));
429 resolver
->SetDnsClient(dns_client
.Pass());
430 resolver_
= resolver
.Pass();
432 start_time_
= base::Time::Now();
437 void GDig::ReplayNextEntry() {
438 DCHECK_LT(replay_log_index_
, replay_log_
.size());
440 base::TimeDelta time_since_start
= base::Time::Now() - start_time_
;
441 while (replay_log_index_
< replay_log_
.size()) {
442 const ReplayLogEntry
& entry
= replay_log_
[replay_log_index_
];
443 if (time_since_start
< entry
.start_time
) {
444 // Delay call to next time and return.
445 base::MessageLoop::current()->PostDelayedTask(
447 base::Bind(&GDig::ReplayNextEntry
, base::Unretained(this)),
448 entry
.start_time
- time_since_start
);
452 HostResolver::RequestInfo
info(HostPortPair(entry
.domain_name
.c_str(), 80));
453 AddressList
* addrlist
= new AddressList();
454 unsigned current_index
= replay_log_index_
;
455 CompletionCallback callback
= base::Bind(&GDig::OnResolveComplete
,
456 base::Unretained(this),
458 base::Owned(addrlist
),
462 int ret
= resolver_
->Resolve(
468 BoundNetLog::Make(log_
.get(), net::NetLog::SOURCE_NONE
));
469 if (ret
!= ERR_IO_PENDING
)
474 void GDig::OnResolveComplete(unsigned entry_index
,
475 AddressList
* address_list
,
476 base::TimeDelta resolve_start_time
,
478 DCHECK_GT(active_resolves_
, 0);
479 DCHECK(address_list
);
480 DCHECK_LT(entry_index
, replay_log_
.size());
482 base::TimeDelta resolve_end_time
= base::Time::Now() - start_time_
;
483 base::TimeDelta resolve_time
= resolve_end_time
- resolve_start_time
;
484 printf("%u %d %d %s %d ",
486 static_cast<int>(resolve_end_time
.InMilliseconds()),
487 static_cast<int>(resolve_time
.InMilliseconds()),
488 replay_log_
[entry_index
].domain_name
.c_str(), val
);
490 std::string error_string
= ErrorToString(val
);
491 printf("%s", error_string
.c_str());
493 for (size_t i
= 0; i
< address_list
->size(); ++i
) {
496 printf("%s", (*address_list
)[i
].ToStringWithoutPort().c_str());
500 if (active_resolves_
== 0 && replay_log_index_
>= replay_log_
.size())
504 void GDig::OnTimeout() {
505 fprintf(stderr
, "Timed out waiting to load the dns config\n");
506 Finish(RESULT_NO_CONFIG
);
513 int main(int argc
, const char* argv
[]) {
515 return dig
.Main(argc
, argv
);