OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include <stdio.h> | |
6 #include <string> | |
7 | |
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_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/dns/host_cache.h" | |
31 #include "net/dns/host_resolver_impl.h" | |
32 #include "net/tools/gdig/file_net_log.h" | |
33 | |
34 #if defined(OS_MACOSX) | |
35 #include "base/mac/scoped_nsautorelease_pool.h" | |
36 #endif | |
37 | |
38 namespace net { | |
39 | |
40 namespace { | |
41 | |
42 bool StringToIPEndPoint(const std::string& ip_address_and_port, | |
43 IPEndPoint* ip_end_point) { | |
44 DCHECK(ip_end_point); | |
45 | |
46 std::string ip; | |
47 int port; | |
48 if (!ParseHostAndPort(ip_address_and_port, &ip, &port)) | |
49 return false; | |
50 if (port == -1) | |
51 port = dns_protocol::kDefaultPort; | |
52 | |
53 net::IPAddressNumber ip_number; | |
54 if (!net::ParseIPLiteralToNumber(ip, &ip_number)) | |
55 return false; | |
56 | |
57 *ip_end_point = net::IPEndPoint(ip_number, static_cast<uint16>(port)); | |
58 return true; | |
59 } | |
60 | |
61 // Convert DnsConfig to human readable text omitting the hosts member. | |
62 std::string DnsConfigToString(const DnsConfig& dns_config) { | |
63 std::string output; | |
64 output.append("search "); | |
65 for (size_t i = 0; i < dns_config.search.size(); ++i) { | |
66 output.append(dns_config.search[i] + " "); | |
67 } | |
68 output.append("\n"); | |
69 | |
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"); | |
73 } | |
74 | |
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"); | |
81 if (dns_config.edns0) | |
82 output.append("options edns0\n"); | |
83 return output; | |
84 } | |
85 | |
86 // Convert DnsConfig hosts member to a human readable text. | |
87 std::string DnsHostsToString(const DnsHosts& dns_hosts) { | |
88 std::string output; | |
89 for (DnsHosts::const_iterator i = dns_hosts.begin(); | |
90 i != dns_hosts.end(); | |
91 ++i) { | |
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"); | |
96 } | |
97 return output; | |
98 } | |
99 | |
100 struct ReplayLogEntry { | |
101 base::TimeDelta start_time; | |
102 std::string domain_name; | |
103 }; | |
104 | |
105 typedef std::vector<ReplayLogEntry> ReplayLog; | |
106 | |
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. | |
110 // | |
111 // The replay log is a text file where each line contains | |
112 // | |
113 // timestamp_in_milliseconds domain_name | |
114 // | |
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. | |
117 // | |
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()); | |
124 return false; | |
125 } | |
126 | |
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 | |
129 // efficiency. | |
130 std::string replay_log_contents; | |
131 base::RemoveChars(original_replay_log_contents, "\r", &replay_log_contents); | |
132 | |
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()) | |
139 continue; | |
140 std::vector<std::string> time_and_name; | |
141 base::SplitString(lines[i], ' ', &time_and_name); | |
142 if (time_and_name.size() != 2) { | |
143 fprintf( | |
144 stderr, | |
145 "[%s %u] replay log should have format 'timestamp domain_name\\n'\n", | |
146 file_path.MaybeAsASCII().c_str(), | |
147 i + 1); | |
148 bad_parse = true; | |
149 continue; | |
150 } | |
151 | |
152 int64 delta_in_milliseconds; | |
153 if (!base::StringToInt64(time_and_name[0], &delta_in_milliseconds)) { | |
154 fprintf( | |
155 stderr, | |
156 "[%s %u] replay log should have format 'timestamp domain_name\\n'\n", | |
157 file_path.MaybeAsASCII().c_str(), | |
158 i + 1); | |
159 bad_parse = true; | |
160 continue; | |
161 } | |
162 | |
163 base::TimeDelta delta = | |
164 base::TimeDelta::FromMilliseconds(delta_in_milliseconds); | |
165 if (delta < previous_delta) { | |
166 fprintf( | |
167 stderr, | |
168 "[%s %u] replay log should be sorted by time\n", | |
169 file_path.MaybeAsASCII().c_str(), | |
170 i + 1); | |
171 bad_parse = true; | |
172 continue; | |
173 } | |
174 | |
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); | |
180 } | |
181 return !bad_parse; | |
182 } | |
183 | |
184 class GDig { | |
185 public: | |
186 GDig(); | |
187 ~GDig(); | |
188 | |
189 enum Result { | |
190 RESULT_NO_RESOLVE = -3, | |
191 RESULT_NO_CONFIG = -2, | |
192 RESULT_WRONG_USAGE = -1, | |
193 RESULT_OK = 0, | |
194 RESULT_PENDING = 1, | |
195 }; | |
196 | |
197 Result Main(int argc, const char* argv[]); | |
198 | |
199 private: | |
200 bool ParseCommandLine(int argc, const char* argv[]); | |
201 | |
202 void Start(); | |
203 void Finish(Result); | |
204 | |
205 void OnDnsConfig(const DnsConfig& dns_config_const); | |
206 void OnResolveComplete(unsigned index, AddressList* address_list, | |
207 base::TimeDelta time_since_start, int val); | |
208 void OnTimeout(); | |
209 void ReplayNextEntry(); | |
210 | |
211 base::TimeDelta config_timeout_; | |
212 bool print_config_; | |
213 bool print_hosts_; | |
214 net::IPEndPoint nameserver_; | |
215 base::TimeDelta timeout_; | |
216 int parallellism_; | |
217 ReplayLog replay_log_; | |
218 unsigned replay_log_index_; | |
219 base::Time start_time_; | |
220 int active_resolves_; | |
221 Result result_; | |
222 | |
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_; | |
228 | |
229 #if defined(OS_MACOSX) | |
230 // Without this there will be a mem leak on osx. | |
231 base::mac::ScopedNSAutoreleasePool scoped_pool_; | |
232 #endif | |
233 | |
234 // Need AtExitManager to support AsWeakPtr (in NetLog). | |
235 base::AtExitManager exit_manager_; | |
236 }; | |
237 | |
238 GDig::GDig() | |
239 : config_timeout_(base::TimeDelta::FromSeconds(5)), | |
240 print_config_(false), | |
241 print_hosts_(false), | |
242 parallellism_(6), | |
243 replay_log_index_(0u), | |
244 active_resolves_(0) { | |
245 } | |
246 | |
247 GDig::~GDig() { | |
248 if (log_) | |
249 log_->RemoveThreadSafeObserver(log_observer_.get()); | |
250 } | |
251 | |
252 GDig::Result GDig::Main(int argc, const char* argv[]) { | |
253 if (!ParseCommandLine(argc, argv)) { | |
254 fprintf(stderr, | |
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>]" | |
262 " [domain_name]\n", | |
263 argv[0]); | |
264 return RESULT_WRONG_USAGE; | |
265 } | |
266 | |
267 base::MessageLoopForIO loop; | |
268 | |
269 result_ = RESULT_PENDING; | |
270 Start(); | |
271 if (result_ == RESULT_PENDING) | |
272 base::MessageLoop::current()->Run(); | |
273 | |
274 // Destroy it while MessageLoopForIO is alive. | |
275 dns_config_service_.reset(); | |
276 return result_; | |
277 } | |
278 | |
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(); | |
283 | |
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"), | |
288 &timeout_seconds); | |
289 if (parsed && timeout_seconds > 0) { | |
290 config_timeout_ = base::TimeDelta::FromSeconds(timeout_seconds); | |
291 } else { | |
292 fprintf(stderr, "Invalid config_timeout parameter\n"); | |
293 return false; | |
294 } | |
295 } | |
296 | |
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; | |
300 | |
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; | |
305 | |
306 if (log_levels.find(log_param) != log_levels.end()) { | |
307 level = log_levels[log_param]; | |
308 } else { | |
309 fprintf(stderr, "Invalid net_log parameter\n"); | |
310 return false; | |
311 } | |
312 } | |
313 log_.reset(new NetLog); | |
314 log_observer_.reset(new FileNetLogObserver(stderr)); | |
315 log_->AddThreadSafeObserver(log_observer_.get(), level); | |
316 } | |
317 | |
318 print_config_ = parsed_command_line.HasSwitch("print_config"); | |
319 print_hosts_ = parsed_command_line.HasSwitch("print_hosts"); | |
320 | |
321 if (parsed_command_line.HasSwitch("nameserver")) { | |
322 std::string nameserver = | |
323 parsed_command_line.GetSwitchValueASCII("nameserver"); | |
324 if (!StringToIPEndPoint(nameserver, &nameserver_)) { | |
325 fprintf(stderr, | |
326 "Cannot parse the namerserver string into an IPEndPoint\n"); | |
327 return false; | |
328 } | |
329 } | |
330 | |
331 if (parsed_command_line.HasSwitch("timeout")) { | |
332 int timeout_millis = 0; | |
333 bool parsed = base::StringToInt( | |
334 parsed_command_line.GetSwitchValueASCII("timeout"), | |
335 &timeout_millis); | |
336 if (parsed && timeout_millis > 0) { | |
337 timeout_ = base::TimeDelta::FromMilliseconds(timeout_millis); | |
338 } else { | |
339 fprintf(stderr, "Invalid timeout parameter\n"); | |
340 return false; | |
341 } | |
342 } | |
343 | |
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_)) | |
348 return false; | |
349 } | |
350 | |
351 if (parsed_command_line.HasSwitch("j")) { | |
352 int parallellism = 0; | |
353 bool parsed = base::StringToInt( | |
354 parsed_command_line.GetSwitchValueASCII("j"), | |
355 ¶llellism); | |
356 if (parsed && parallellism > 0) { | |
357 parallellism_ = parallellism; | |
358 } else { | |
359 fprintf(stderr, "Invalid parallellism parameter\n"); | |
360 } | |
361 } | |
362 | |
363 if (parsed_command_line.GetArgs().size() == 1) { | |
364 ReplayLogEntry entry; | |
365 entry.start_time = base::TimeDelta(); | |
366 #if defined(OS_WIN) | |
367 entry.domain_name = base::UTF16ToASCII(parsed_command_line.GetArgs()[0]); | |
368 #else | |
369 entry.domain_name = parsed_command_line.GetArgs()[0]; | |
370 #endif | |
371 replay_log_.push_back(entry); | |
372 } else if (parsed_command_line.GetArgs().size() != 0) { | |
373 return false; | |
374 } | |
375 return print_config_ || print_hosts_ || !replay_log_.empty(); | |
376 } | |
377 | |
378 void GDig::Start() { | |
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); | |
384 } else { | |
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_); | |
392 } | |
393 } | |
394 | |
395 void GDig::Finish(Result result) { | |
396 DCHECK_NE(RESULT_PENDING, result); | |
397 result_ = result; | |
398 if (base::MessageLoop::current()) | |
399 base::MessageLoop::current()->Quit(); | |
400 } | |
401 | |
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; | |
406 | |
407 if (timeout_.InMilliseconds() > 0) | |
408 dns_config.timeout = timeout_; | |
409 if (print_config_) { | |
410 printf("# Dns Configuration\n" | |
411 "%s", DnsConfigToString(dns_config).c_str()); | |
412 } | |
413 if (print_hosts_) { | |
414 printf("# Host Database\n" | |
415 "%s", DnsHostsToString(dns_config.hosts).c_str()); | |
416 } | |
417 | |
418 if (replay_log_.empty()) { | |
419 Finish(RESULT_OK); | |
420 return; | |
421 } | |
422 | |
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(); | |
432 | |
433 start_time_ = base::Time::Now(); | |
434 | |
435 ReplayNextEntry(); | |
436 } | |
437 | |
438 void GDig::ReplayNextEntry() { | |
439 DCHECK_LT(replay_log_index_, replay_log_.size()); | |
440 | |
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( | |
447 FROM_HERE, | |
448 base::Bind(&GDig::ReplayNextEntry, base::Unretained(this)), | |
449 entry.start_time - time_since_start); | |
450 return; | |
451 } | |
452 | |
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), | |
458 current_index, | |
459 base::Owned(addrlist), | |
460 time_since_start); | |
461 ++active_resolves_; | |
462 ++replay_log_index_; | |
463 int ret = resolver_->Resolve( | |
464 info, | |
465 DEFAULT_PRIORITY, | |
466 addrlist, | |
467 callback, | |
468 NULL, | |
469 BoundNetLog::Make(log_.get(), net::NetLog::SOURCE_NONE)); | |
470 if (ret != ERR_IO_PENDING) | |
471 callback.Run(ret); | |
472 } | |
473 } | |
474 | |
475 void GDig::OnResolveComplete(unsigned entry_index, | |
476 AddressList* address_list, | |
477 base::TimeDelta resolve_start_time, | |
478 int val) { | |
479 DCHECK_GT(active_resolves_, 0); | |
480 DCHECK(address_list); | |
481 DCHECK_LT(entry_index, replay_log_.size()); | |
482 --active_resolves_; | |
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 ", | |
486 entry_index, | |
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); | |
490 if (val != OK) { | |
491 std::string error_string = ErrorToString(val); | |
492 printf("%s", error_string.c_str()); | |
493 } else { | |
494 for (size_t i = 0; i < address_list->size(); ++i) { | |
495 if (i != 0) | |
496 printf(" "); | |
497 printf("%s", (*address_list)[i].ToStringWithoutPort().c_str()); | |
498 } | |
499 } | |
500 printf("\n"); | |
501 if (active_resolves_ == 0 && replay_log_index_ >= replay_log_.size()) | |
502 Finish(RESULT_OK); | |
503 } | |
504 | |
505 void GDig::OnTimeout() { | |
506 fprintf(stderr, "Timed out waiting to load the dns config\n"); | |
507 Finish(RESULT_NO_CONFIG); | |
508 } | |
509 | |
510 } // empty namespace | |
511 | |
512 } // namespace net | |
513 | |
514 int main(int argc, const char* argv[]) { | |
515 net::GDig dig; | |
516 return dig.Main(argc, argv); | |
517 } | |
OLD | NEW |