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/file_util.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/message_loop.h"
15 #include "base/string_number_conversions.h"
16 #include "base/string_split.h"
17 #include "base/string_util.h"
18 #include "base/stringprintf.h"
19 #include "base/time.h"
20 #include "net/base/address_list.h"
21 #include "net/base/host_cache.h"
22 #include "net/base/host_resolver_impl.h"
23 #include "net/base/ip_endpoint.h"
24 #include "net/base/net_errors.h"
25 #include "net/base/net_log.h"
26 #include "net/base/net_util.h"
27 #include "net/dns/dns_client.h"
28 #include "net/dns/dns_config_service.h"
29 #include "net/dns/dns_protocol.h"
30 #include "net/tools/gdig/file_net_log.h"
32 #if defined(OS_MACOSX)
33 #include "base/mac/scoped_nsautorelease_pool.h"
40 bool StringToIPEndPoint(const std::string
& ip_address_and_port
,
41 IPEndPoint
* ip_end_point
) {
46 if (!ParseHostAndPort(ip_address_and_port
, &ip
, &port
))
49 port
= dns_protocol::kDefaultPort
;
51 net::IPAddressNumber ip_number
;
52 if (!net::ParseIPLiteralToNumber(ip
, &ip_number
))
55 *ip_end_point
= net::IPEndPoint(ip_number
, port
);
59 // Convert DnsConfig to human readable text omitting the hosts member.
60 std::string
DnsConfigToString(const DnsConfig
& dns_config
) {
62 output
.append("search ");
63 for (size_t i
= 0; i
< dns_config
.search
.size(); ++i
) {
64 output
.append(dns_config
.search
[i
] + " ");
68 for (size_t i
= 0; i
< dns_config
.nameservers
.size(); ++i
) {
69 output
.append("nameserver ");
70 output
.append(dns_config
.nameservers
[i
].ToString()).append("\n");
73 base::StringAppendF(&output
, "options ndots:%d\n", dns_config
.ndots
);
74 base::StringAppendF(&output
, "options timeout:%d\n",
75 static_cast<int>(dns_config
.timeout
.InMilliseconds()));
76 base::StringAppendF(&output
, "options attempts:%d\n", dns_config
.attempts
);
77 if (dns_config
.rotate
)
78 output
.append("options rotate\n");
80 output
.append("options edns0\n");
84 // Convert DnsConfig hosts member to a human readable text.
85 std::string
DnsHostsToString(const DnsHosts
& dns_hosts
) {
87 for (DnsHosts::const_iterator i
= dns_hosts
.begin();
90 const DnsHostsKey
& key
= i
->first
;
91 std::string host_name
= key
.first
;
92 output
.append(IPEndPoint(i
->second
, -1).ToStringWithoutPort());
93 output
.append(" ").append(host_name
).append("\n");
98 struct ReplayLogEntry
{
99 base::TimeDelta start_time
;
100 std::string domain_name
;
103 typedef std::vector
<ReplayLogEntry
> ReplayLog
;
105 // Loads and parses a replay log file and fills |replay_log| with a structured
106 // representation. Returns whether the operation was successful. If not, the
107 // contents of |replay_log| are undefined.
109 // The replay log is a text file where each line contains
111 // timestamp_in_milliseconds domain_name
113 // The timestamp_in_milliseconds needs to be an integral delta from start of
114 // resolution and is in milliseconds. domain_name is the name to be resolved.
116 // The file should be sorted by timestamp in ascending time.
117 bool LoadReplayLog(const FilePath
& file_path
, ReplayLog
* replay_log
) {
118 std::string original_replay_log_contents
;
119 if (!file_util::ReadFileToString(file_path
, &original_replay_log_contents
)) {
120 fprintf(stderr
, "Unable to open replay file %s\n",
121 file_path
.MaybeAsASCII().c_str());
125 // Strip out \r characters for Windows files. This isn't as efficient as a
126 // smarter line splitter, but this particular use does not need to target
128 std::string replay_log_contents
;
129 RemoveChars(original_replay_log_contents
, "\r", &replay_log_contents
);
131 std::vector
<std::string
> lines
;
132 base::SplitString(replay_log_contents
, '\n', &lines
);
133 base::TimeDelta previous_delta
;
134 bool bad_parse
= false;
135 for (size_t i
= 0; i
< lines
.size(); ++i
) {
136 if (lines
[i
].empty())
138 std::vector
<std::string
> time_and_name
;
139 base::SplitString(lines
[i
], ' ', &time_and_name
);
140 if (time_and_name
.size() != 2) {
143 "[%s %zu] replay log should have format 'timestamp domain_name\\n'\n",
144 file_path
.MaybeAsASCII().c_str(),
150 int64 delta_in_milliseconds
;
151 if (!base::StringToInt64(time_and_name
[0], &delta_in_milliseconds
)) {
154 "[%s %zu] replay log should have format 'timestamp domain_name\\n'\n",
155 file_path
.MaybeAsASCII().c_str(),
161 base::TimeDelta delta
=
162 base::TimeDelta::FromMilliseconds(delta_in_milliseconds
);
163 if (delta
< previous_delta
) {
166 "[%s %zu] replay log should be sorted by time\n",
167 file_path
.MaybeAsASCII().c_str(),
173 previous_delta
= delta
;
174 ReplayLogEntry entry
;
175 entry
.start_time
= delta
;
176 entry
.domain_name
= time_and_name
[1];
177 replay_log
->push_back(entry
);
187 RESULT_NO_RESOLVE
= -3,
188 RESULT_NO_CONFIG
= -2,
189 RESULT_WRONG_USAGE
= -1,
194 Result
Main(int argc
, const char* argv
[]);
197 bool ParseCommandLine(int argc
, const char* argv
[]);
202 void OnDnsConfig(const DnsConfig
& dns_config_const
);
203 void OnResolveComplete(size_t index
, AddressList
* address_list
,
204 base::TimeDelta time_since_start
, int val
);
206 void ReplayNextEntry();
208 base::TimeDelta config_timeout_
;
211 net::IPEndPoint nameserver_
;
212 base::TimeDelta timeout_
;
214 ReplayLog replay_log_
;
215 size_t replay_log_index_
;
216 base::Time start_time_
;
217 int active_resolves_
;
220 base::CancelableClosure timeout_closure_
;
221 scoped_ptr
<DnsConfigService
> dns_config_service_
;
222 scoped_ptr
<FileNetLog
> log_
;
223 scoped_ptr
<HostResolver
> resolver_
;
227 : config_timeout_(base::TimeDelta::FromSeconds(5)),
228 print_config_(false),
231 replay_log_index_(0u),
232 active_resolves_(0) {
235 GDig::Result
GDig::Main(int argc
, const char* argv
[]) {
236 if (!ParseCommandLine(argc
, argv
)) {
238 "usage: %s [--net_log[=<basic|no_bytes|all>]]"
239 " [--print_config] [--print_hosts]"
240 " [--nameserver=<ip_address[:port]>]"
241 " [--timeout=<milliseconds>]"
242 " [--config_timeout=<seconds>]"
243 " [--j=<parallel resolves>]"
244 " [--replay_file=<path>]"
247 return RESULT_WRONG_USAGE
;
250 #if defined(OS_MACOSX)
251 // Without this there will be a mem leak on osx.
252 base::mac::ScopedNSAutoreleasePool scoped_pool
;
255 base::AtExitManager exit_manager
;
256 MessageLoopForIO loop
;
258 result_
= RESULT_PENDING
;
260 if (result_
== RESULT_PENDING
)
261 MessageLoop::current()->Run();
263 // Destroy it while MessageLoopForIO is alive.
264 dns_config_service_
.reset();
268 bool GDig::ParseCommandLine(int argc
, const char* argv
[]) {
269 CommandLine::Init(argc
, argv
);
270 const CommandLine
& parsed_command_line
= *CommandLine::ForCurrentProcess();
272 if (parsed_command_line
.HasSwitch("config_timeout")) {
273 int timeout_seconds
= 0;
274 bool parsed
= base::StringToInt(
275 parsed_command_line
.GetSwitchValueASCII("config_timeout"),
277 if (parsed
&& timeout_seconds
> 0) {
278 config_timeout_
= base::TimeDelta::FromSeconds(timeout_seconds
);
280 fprintf(stderr
, "Invalid config_timeout parameter\n");
285 if (parsed_command_line
.HasSwitch("net_log")) {
286 std::string log_param
= parsed_command_line
.GetSwitchValueASCII("net_log");
287 NetLog::LogLevel level
= NetLog::LOG_ALL_BUT_BYTES
;
289 if (log_param
.length() > 0) {
290 std::map
<std::string
, NetLog::LogLevel
> log_levels
;
291 log_levels
["all"] = NetLog::LOG_ALL
;
292 log_levels
["no_bytes"] = NetLog::LOG_ALL_BUT_BYTES
;
293 log_levels
["basic"] = NetLog::LOG_BASIC
;
295 if (log_levels
.find(log_param
) != log_levels
.end()) {
296 level
= log_levels
[log_param
];
298 fprintf(stderr
, "Invalid net_log parameter\n");
302 log_
.reset(new FileNetLog(stderr
, level
));
305 print_config_
= parsed_command_line
.HasSwitch("print_config");
306 print_hosts_
= parsed_command_line
.HasSwitch("print_hosts");
308 if (parsed_command_line
.HasSwitch("nameserver")) {
309 std::string nameserver
=
310 parsed_command_line
.GetSwitchValueASCII("nameserver");
311 if (!StringToIPEndPoint(nameserver
, &nameserver_
)) {
313 "Cannot parse the namerserver string into an IPEndPoint\n");
318 if (parsed_command_line
.HasSwitch("timeout")) {
319 int timeout_millis
= 0;
320 bool parsed
= base::StringToInt(
321 parsed_command_line
.GetSwitchValueASCII("timeout"),
323 if (parsed
&& timeout_millis
> 0) {
324 timeout_
= base::TimeDelta::FromMilliseconds(timeout_millis
);
326 fprintf(stderr
, "Invalid timeout parameter\n");
331 if (parsed_command_line
.HasSwitch("replay_file")) {
332 FilePath replay_path
=
333 parsed_command_line
.GetSwitchValuePath("replay_file");
334 if (!LoadReplayLog(replay_path
, &replay_log_
))
338 if (parsed_command_line
.HasSwitch("j")) {
339 int parallellism
= 0;
340 bool parsed
= base::StringToInt(
341 parsed_command_line
.GetSwitchValueASCII("j"),
343 if (parsed
&& parallellism
> 0) {
344 parallellism_
= parallellism
;
346 fprintf(stderr
, "Invalid parallellism parameter\n");
350 if (parsed_command_line
.GetArgs().size() == 1) {
351 ReplayLogEntry entry
;
352 entry
.start_time
= base::TimeDelta();
354 entry
.domain_name
= WideToASCII(parsed_command_line
.GetArgs()[0]);
356 entry
.domain_name
= parsed_command_line
.GetArgs()[0];
358 replay_log_
.push_back(entry
);
359 } else if (parsed_command_line
.GetArgs().size() != 0) {
362 return print_config_
|| print_hosts_
|| !replay_log_
.empty();
366 if (nameserver_
.address().size() > 0) {
367 DnsConfig dns_config
;
368 dns_config
.attempts
= 1;
369 dns_config
.nameservers
.push_back(nameserver_
);
370 OnDnsConfig(dns_config
);
372 dns_config_service_
= DnsConfigService::CreateSystemService();
373 dns_config_service_
->ReadConfig(base::Bind(&GDig::OnDnsConfig
,
374 base::Unretained(this)));
375 timeout_closure_
.Reset(base::Bind(&GDig::OnTimeout
,
376 base::Unretained(this)));
377 MessageLoop::current()->PostDelayedTask(
379 timeout_closure_
.callback(),
384 void GDig::Finish(Result result
) {
385 DCHECK_NE(RESULT_PENDING
, result
);
387 if (MessageLoop::current())
388 MessageLoop::current()->Quit();
391 void GDig::OnDnsConfig(const DnsConfig
& dns_config_const
) {
392 timeout_closure_
.Cancel();
393 DCHECK(dns_config_const
.IsValid());
394 DnsConfig dns_config
= dns_config_const
;
396 if (timeout_
.InMilliseconds() > 0)
397 dns_config
.timeout
= timeout_
;
399 printf("# Dns Configuration\n"
400 "%s", DnsConfigToString(dns_config
).c_str());
403 printf("# Host Database\n"
404 "%s", DnsHostsToString(dns_config
.hosts
).c_str());
407 if (replay_log_
.empty()) {
412 scoped_ptr
<DnsClient
> dns_client(DnsClient::CreateClient(NULL
));
413 dns_client
->SetConfig(dns_config
);
414 scoped_ptr
<HostResolverImpl
> resolver(
415 new HostResolverImpl(
416 HostCache::CreateDefaultCache(),
417 PrioritizedDispatcher::Limits(NUM_PRIORITIES
, parallellism_
),
418 HostResolverImpl::ProcTaskParams(NULL
, 1),
420 resolver
->SetDnsClient(dns_client
.Pass());
421 resolver_
= resolver
.Pass();
423 start_time_
= base::Time::Now();
428 void GDig::ReplayNextEntry() {
429 DCHECK_LT(replay_log_index_
, replay_log_
.size());
431 base::TimeDelta time_since_start
= base::Time::Now() - start_time_
;
432 while (replay_log_index_
< replay_log_
.size()) {
433 const ReplayLogEntry
& entry
= replay_log_
[replay_log_index_
];
434 if (time_since_start
< entry
.start_time
) {
435 // Delay call to next time and return.
436 MessageLoop::current()->PostDelayedTask(
438 base::Bind(&GDig::ReplayNextEntry
, base::Unretained(this)),
439 entry
.start_time
- time_since_start
);
443 HostResolver::RequestInfo
info(HostPortPair(entry
.domain_name
.c_str(), 80));
444 AddressList
* addrlist
= new AddressList();
445 size_t current_index
= replay_log_index_
;
446 CompletionCallback callback
= base::Bind(&GDig::OnResolveComplete
,
447 base::Unretained(this),
449 base::Owned(addrlist
),
453 int ret
= resolver_
->Resolve(
454 info
, addrlist
, callback
, NULL
,
455 BoundNetLog::Make(log_
.get(), net::NetLog::SOURCE_NONE
));
456 if (ret
!= ERR_IO_PENDING
)
461 void GDig::OnResolveComplete(size_t entry_index
,
462 AddressList
* address_list
,
463 base::TimeDelta resolve_start_time
,
465 DCHECK_GT(active_resolves_
, 0);
466 DCHECK(address_list
);
467 DCHECK_LT(entry_index
, replay_log_
.size());
469 base::TimeDelta resolve_end_time
= base::Time::Now() - start_time_
;
470 base::TimeDelta resolve_time
= resolve_end_time
- resolve_start_time
;
471 printf("%zu %d %d %s %d ",
473 static_cast<int>(resolve_end_time
.InMilliseconds()),
474 static_cast<int>(resolve_time
.InMilliseconds()),
475 replay_log_
[entry_index
].domain_name
.c_str(), val
);
477 printf("%s", ErrorToString(val
));
479 for (size_t i
= 0; i
< address_list
->size(); ++i
) {
482 printf("%s", (*address_list
)[i
].ToStringWithoutPort().c_str());
486 if (active_resolves_
== 0 && replay_log_index_
>= replay_log_
.size())
490 void GDig::OnTimeout() {
491 fprintf(stderr
, "Timed out waiting to load the dns config\n");
492 Finish(RESULT_NO_CONFIG
);
499 int main(int argc
, const char* argv
[]) {
501 return dig
.Main(argc
, argv
);