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/location.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/single_thread_task_runner.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_split.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/thread_task_runner_handle.h"
24 #include "base/time/time.h"
25 #include "net/base/address_list.h"
26 #include "net/base/ip_endpoint.h"
27 #include "net/base/net_errors.h"
28 #include "net/base/net_util.h"
29 #include "net/dns/dns_client.h"
30 #include "net/dns/dns_config_service.h"
31 #include "net/dns/dns_protocol.h"
32 #include "net/dns/host_cache.h"
33 #include "net/dns/host_resolver_impl.h"
34 #include "net/log/net_log.h"
35 #include "net/tools/gdig/file_net_log.h"
37 #if defined(OS_MACOSX)
38 #include "base/mac/scoped_nsautorelease_pool.h"
45 bool StringToIPEndPoint(const std::string
& ip_address_and_port
,
46 IPEndPoint
* ip_end_point
) {
51 if (!ParseHostAndPort(ip_address_and_port
, &ip
, &port
))
54 port
= dns_protocol::kDefaultPort
;
56 net::IPAddressNumber ip_number
;
57 if (!net::ParseIPLiteralToNumber(ip
, &ip_number
))
60 *ip_end_point
= net::IPEndPoint(ip_number
, static_cast<uint16
>(port
));
64 // Convert DnsConfig to human readable text omitting the hosts member.
65 std::string
DnsConfigToString(const DnsConfig
& dns_config
) {
67 output
.append("search ");
68 for (size_t i
= 0; i
< dns_config
.search
.size(); ++i
) {
69 output
.append(dns_config
.search
[i
] + " ");
73 for (size_t i
= 0; i
< dns_config
.nameservers
.size(); ++i
) {
74 output
.append("nameserver ");
75 output
.append(dns_config
.nameservers
[i
].ToString()).append("\n");
78 base::StringAppendF(&output
, "options ndots:%d\n", dns_config
.ndots
);
79 base::StringAppendF(&output
, "options timeout:%d\n",
80 static_cast<int>(dns_config
.timeout
.InMilliseconds()));
81 base::StringAppendF(&output
, "options attempts:%d\n", dns_config
.attempts
);
82 if (dns_config
.rotate
)
83 output
.append("options rotate\n");
85 output
.append("options edns0\n");
89 // Convert DnsConfig hosts member to a human readable text.
90 std::string
DnsHostsToString(const DnsHosts
& dns_hosts
) {
92 for (DnsHosts::const_iterator i
= dns_hosts
.begin();
95 const DnsHostsKey
& key
= i
->first
;
96 std::string host_name
= key
.first
;
97 output
.append(IPEndPoint(i
->second
, 0).ToStringWithoutPort());
98 output
.append(" ").append(host_name
).append("\n");
103 struct ReplayLogEntry
{
104 base::TimeDelta start_time
;
105 std::string domain_name
;
108 typedef std::vector
<ReplayLogEntry
> ReplayLog
;
110 // Loads and parses a replay log file and fills |replay_log| with a structured
111 // representation. Returns whether the operation was successful. If not, the
112 // contents of |replay_log| are undefined.
114 // The replay log is a text file where each line contains
116 // timestamp_in_milliseconds domain_name
118 // The timestamp_in_milliseconds needs to be an integral delta from start of
119 // resolution and is in milliseconds. domain_name is the name to be resolved.
121 // The file should be sorted by timestamp in ascending time.
122 bool LoadReplayLog(const base::FilePath
& file_path
, ReplayLog
* replay_log
) {
123 std::string original_replay_log_contents
;
124 if (!base::ReadFileToString(file_path
, &original_replay_log_contents
)) {
125 fprintf(stderr
, "Unable to open replay file %s\n",
126 file_path
.MaybeAsASCII().c_str());
130 // Strip out \r characters for Windows files. This isn't as efficient as a
131 // smarter line splitter, but this particular use does not need to target
133 std::string replay_log_contents
;
134 base::RemoveChars(original_replay_log_contents
, "\r", &replay_log_contents
);
136 std::vector
<std::string
> lines
= base::SplitString(
137 replay_log_contents
, "\n", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
138 base::TimeDelta previous_delta
;
139 bool bad_parse
= false;
140 for (unsigned i
= 0; i
< lines
.size(); ++i
) {
141 if (lines
[i
].empty())
143 std::vector
<std::string
> time_and_name
= base::SplitString(
144 lines
[i
], " ", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
145 if (time_and_name
.size() != 2) {
148 "[%s %u] replay log should have format 'timestamp domain_name\\n'\n",
149 file_path
.MaybeAsASCII().c_str(),
155 int64 delta_in_milliseconds
;
156 if (!base::StringToInt64(time_and_name
[0], &delta_in_milliseconds
)) {
159 "[%s %u] replay log should have format 'timestamp domain_name\\n'\n",
160 file_path
.MaybeAsASCII().c_str(),
166 base::TimeDelta delta
=
167 base::TimeDelta::FromMilliseconds(delta_in_milliseconds
);
168 if (delta
< previous_delta
) {
171 "[%s %u] replay log should be sorted by time\n",
172 file_path
.MaybeAsASCII().c_str(),
178 previous_delta
= delta
;
179 ReplayLogEntry entry
;
180 entry
.start_time
= delta
;
181 entry
.domain_name
= time_and_name
[1];
182 replay_log
->push_back(entry
);
193 RESULT_NO_RESOLVE
= -3,
194 RESULT_NO_CONFIG
= -2,
195 RESULT_WRONG_USAGE
= -1,
200 Result
Main(int argc
, const char* argv
[]);
203 bool ParseCommandLine(int argc
, const char* argv
[]);
208 void OnDnsConfig(const DnsConfig
& dns_config_const
);
209 void OnResolveComplete(unsigned index
, AddressList
* address_list
,
210 base::TimeDelta time_since_start
, int val
);
212 void ReplayNextEntry();
214 base::TimeDelta config_timeout_
;
217 net::IPEndPoint nameserver_
;
218 base::TimeDelta timeout_
;
220 ReplayLog replay_log_
;
221 unsigned replay_log_index_
;
222 base::Time start_time_
;
223 int active_resolves_
;
226 base::CancelableClosure timeout_closure_
;
227 scoped_ptr
<DnsConfigService
> dns_config_service_
;
228 scoped_ptr
<FileNetLogObserver
> log_observer_
;
229 scoped_ptr
<NetLog
> log_
;
230 scoped_ptr
<HostResolver
> resolver_
;
232 #if defined(OS_MACOSX)
233 // Without this there will be a mem leak on osx.
234 base::mac::ScopedNSAutoreleasePool scoped_pool_
;
237 // Need AtExitManager to support AsWeakPtr (in NetLog).
238 base::AtExitManager exit_manager_
;
242 : config_timeout_(base::TimeDelta::FromSeconds(5)),
243 print_config_(false),
246 replay_log_index_(0u),
247 active_resolves_(0) {
252 log_
->DeprecatedRemoveObserver(log_observer_
.get());
255 GDig::Result
GDig::Main(int argc
, const char* argv
[]) {
256 if (!ParseCommandLine(argc
, argv
)) {
258 "usage: %s [--net_log[=<basic|no_bytes|all>]]"
259 " [--print_config] [--print_hosts]"
260 " [--nameserver=<ip_address[:port]>]"
261 " [--timeout=<milliseconds>]"
262 " [--config_timeout=<seconds>]"
263 " [--j=<parallel resolves>]"
264 " [--replay_file=<path>]"
267 return RESULT_WRONG_USAGE
;
270 base::MessageLoopForIO loop
;
272 result_
= RESULT_PENDING
;
274 if (result_
== RESULT_PENDING
)
275 base::MessageLoop::current()->Run();
277 // Destroy it while MessageLoopForIO is alive.
278 dns_config_service_
.reset();
282 bool GDig::ParseCommandLine(int argc
, const char* argv
[]) {
283 base::CommandLine::Init(argc
, argv
);
284 const base::CommandLine
& parsed_command_line
=
285 *base::CommandLine::ForCurrentProcess();
287 if (parsed_command_line
.HasSwitch("config_timeout")) {
288 int timeout_seconds
= 0;
289 bool parsed
= base::StringToInt(
290 parsed_command_line
.GetSwitchValueASCII("config_timeout"),
292 if (parsed
&& timeout_seconds
> 0) {
293 config_timeout_
= base::TimeDelta::FromSeconds(timeout_seconds
);
295 fprintf(stderr
, "Invalid config_timeout parameter\n");
300 if (parsed_command_line
.HasSwitch("net_log")) {
301 std::string log_param
= parsed_command_line
.GetSwitchValueASCII("net_log");
302 NetLogCaptureMode capture_mode
=
303 NetLogCaptureMode::IncludeCookiesAndCredentials();
305 if (log_param
.length() > 0) {
306 std::map
<std::string
, NetLogCaptureMode
> capture_modes
;
307 capture_modes
["all"] = NetLogCaptureMode::IncludeSocketBytes();
308 capture_modes
["no_bytes"] =
309 NetLogCaptureMode::IncludeCookiesAndCredentials();
311 if (capture_modes
.find(log_param
) != capture_modes
.end()) {
312 capture_mode
= capture_modes
[log_param
];
314 fprintf(stderr
, "Invalid net_log parameter\n");
318 log_
.reset(new NetLog
);
319 log_observer_
.reset(new FileNetLogObserver(stderr
));
320 log_
->DeprecatedAddObserver(log_observer_
.get(), capture_mode
);
323 print_config_
= parsed_command_line
.HasSwitch("print_config");
324 print_hosts_
= parsed_command_line
.HasSwitch("print_hosts");
326 if (parsed_command_line
.HasSwitch("nameserver")) {
327 std::string nameserver
=
328 parsed_command_line
.GetSwitchValueASCII("nameserver");
329 if (!StringToIPEndPoint(nameserver
, &nameserver_
)) {
331 "Cannot parse the namerserver string into an IPEndPoint\n");
336 if (parsed_command_line
.HasSwitch("timeout")) {
337 int timeout_millis
= 0;
338 bool parsed
= base::StringToInt(
339 parsed_command_line
.GetSwitchValueASCII("timeout"),
341 if (parsed
&& timeout_millis
> 0) {
342 timeout_
= base::TimeDelta::FromMilliseconds(timeout_millis
);
344 fprintf(stderr
, "Invalid timeout parameter\n");
349 if (parsed_command_line
.HasSwitch("replay_file")) {
350 base::FilePath replay_path
=
351 parsed_command_line
.GetSwitchValuePath("replay_file");
352 if (!LoadReplayLog(replay_path
, &replay_log_
))
356 if (parsed_command_line
.HasSwitch("j")) {
357 int parallellism
= 0;
358 bool parsed
= base::StringToInt(
359 parsed_command_line
.GetSwitchValueASCII("j"),
361 if (parsed
&& parallellism
> 0) {
362 parallellism_
= parallellism
;
364 fprintf(stderr
, "Invalid parallellism parameter\n");
368 if (parsed_command_line
.GetArgs().size() == 1) {
369 ReplayLogEntry entry
;
370 entry
.start_time
= base::TimeDelta();
372 entry
.domain_name
= base::UTF16ToASCII(parsed_command_line
.GetArgs()[0]);
374 entry
.domain_name
= parsed_command_line
.GetArgs()[0];
376 replay_log_
.push_back(entry
);
377 } else if (parsed_command_line
.GetArgs().size() != 0) {
380 return print_config_
|| print_hosts_
|| !replay_log_
.empty();
384 if (nameserver_
.address().size() > 0) {
385 DnsConfig dns_config
;
386 dns_config
.attempts
= 1;
387 dns_config
.nameservers
.push_back(nameserver_
);
388 OnDnsConfig(dns_config
);
390 dns_config_service_
= DnsConfigService::CreateSystemService();
391 dns_config_service_
->ReadConfig(base::Bind(&GDig::OnDnsConfig
,
392 base::Unretained(this)));
393 timeout_closure_
.Reset(base::Bind(&GDig::OnTimeout
,
394 base::Unretained(this)));
395 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
396 FROM_HERE
, timeout_closure_
.callback(), config_timeout_
);
400 void GDig::Finish(Result result
) {
401 DCHECK_NE(RESULT_PENDING
, result
);
403 if (base::MessageLoop::current())
404 base::MessageLoop::current()->Quit();
407 void GDig::OnDnsConfig(const DnsConfig
& dns_config_const
) {
408 timeout_closure_
.Cancel();
409 DCHECK(dns_config_const
.IsValid());
410 DnsConfig dns_config
= dns_config_const
;
412 if (timeout_
.InMilliseconds() > 0)
413 dns_config
.timeout
= timeout_
;
415 printf("# Dns Configuration\n"
416 "%s", DnsConfigToString(dns_config
).c_str());
419 printf("# Host Database\n"
420 "%s", DnsHostsToString(dns_config
.hosts
).c_str());
423 if (replay_log_
.empty()) {
428 scoped_ptr
<DnsClient
> dns_client(DnsClient::CreateClient(NULL
));
429 dns_client
->SetConfig(dns_config
);
430 HostResolver::Options options
;
431 options
.max_concurrent_resolves
= parallellism_
;
432 options
.max_retry_attempts
= 1u;
433 scoped_ptr
<HostResolverImpl
> resolver(
434 new HostResolverImpl(options
, log_
.get()));
435 resolver
->SetDnsClient(dns_client
.Pass());
436 resolver_
= resolver
.Pass();
438 start_time_
= base::Time::Now();
443 void GDig::ReplayNextEntry() {
444 DCHECK_LT(replay_log_index_
, replay_log_
.size());
446 base::TimeDelta time_since_start
= base::Time::Now() - start_time_
;
447 while (replay_log_index_
< replay_log_
.size()) {
448 const ReplayLogEntry
& entry
= replay_log_
[replay_log_index_
];
449 if (time_since_start
< entry
.start_time
) {
450 // Delay call to next time and return.
451 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
452 FROM_HERE
, base::Bind(&GDig::ReplayNextEntry
, base::Unretained(this)),
453 entry
.start_time
- time_since_start
);
457 HostResolver::RequestInfo
info(HostPortPair(entry
.domain_name
.c_str(), 80));
458 AddressList
* addrlist
= new AddressList();
459 unsigned current_index
= replay_log_index_
;
460 CompletionCallback callback
= base::Bind(&GDig::OnResolveComplete
,
461 base::Unretained(this),
463 base::Owned(addrlist
),
467 int ret
= resolver_
->Resolve(
473 BoundNetLog::Make(log_
.get(), net::NetLog::SOURCE_NONE
));
474 if (ret
!= ERR_IO_PENDING
)
479 void GDig::OnResolveComplete(unsigned entry_index
,
480 AddressList
* address_list
,
481 base::TimeDelta resolve_start_time
,
483 DCHECK_GT(active_resolves_
, 0);
484 DCHECK(address_list
);
485 DCHECK_LT(entry_index
, replay_log_
.size());
487 base::TimeDelta resolve_end_time
= base::Time::Now() - start_time_
;
488 base::TimeDelta resolve_time
= resolve_end_time
- resolve_start_time
;
489 printf("%u %d %d %s %d ",
491 static_cast<int>(resolve_end_time
.InMilliseconds()),
492 static_cast<int>(resolve_time
.InMilliseconds()),
493 replay_log_
[entry_index
].domain_name
.c_str(), val
);
495 std::string error_string
= ErrorToString(val
);
496 printf("%s", error_string
.c_str());
498 for (size_t i
= 0; i
< address_list
->size(); ++i
) {
501 printf("%s", (*address_list
)[i
].ToStringWithoutPort().c_str());
505 if (active_resolves_
== 0 && replay_log_index_
>= replay_log_
.size())
509 void GDig::OnTimeout() {
510 fprintf(stderr
, "Timed out waiting to load the dns config\n");
511 Finish(RESULT_NO_CONFIG
);
518 int main(int argc
, const char* argv
[]) {
520 return dig
.Main(argc
, argv
);