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