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/time/time.h"
20#include "net/base/address_list.h"
21#include "net/base/ip_endpoint.h"
22#include "net/base/net_errors.h"
23#include "net/base/net_log.h"
24#include "net/base/net_util.h"
25#include "net/dns/dns_client.h"
26#include "net/dns/dns_config_service.h"
27#include "net/dns/dns_protocol.h"
28#include "net/dns/host_cache.h"
29#include "net/dns/host_resolver_impl.h"
30#include "net/tools/gdig/file_net_log.h"
31
32#if defined(OS_MACOSX)
33#include "base/mac/scoped_nsautorelease_pool.h"
34#endif
35
36namespace net {
37
38namespace {
39
40bool StringToIPEndPoint(const std::string& ip_address_and_port,
41                        IPEndPoint* ip_end_point) {
42  DCHECK(ip_end_point);
43
44  std::string ip;
45  int port;
46  if (!ParseHostAndPort(ip_address_and_port, &ip, &port))
47    return false;
48  if (port == -1)
49    port = dns_protocol::kDefaultPort;
50
51  net::IPAddressNumber ip_number;
52  if (!net::ParseIPLiteralToNumber(ip, &ip_number))
53    return false;
54
55  *ip_end_point = net::IPEndPoint(ip_number, port);
56  return true;
57}
58
59// Convert DnsConfig to human readable text omitting the hosts member.
60std::string DnsConfigToString(const DnsConfig& dns_config) {
61  std::string output;
62  output.append("search ");
63  for (size_t i = 0; i < dns_config.search.size(); ++i) {
64    output.append(dns_config.search[i] + " ");
65  }
66  output.append("\n");
67
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");
71  }
72
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");
79  if (dns_config.edns0)
80    output.append("options edns0\n");
81  return output;
82}
83
84// Convert DnsConfig hosts member to a human readable text.
85std::string DnsHostsToString(const DnsHosts& dns_hosts) {
86  std::string output;
87  for (DnsHosts::const_iterator i = dns_hosts.begin();
88       i != dns_hosts.end();
89       ++i) {
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");
94  }
95  return output;
96}
97
98struct ReplayLogEntry {
99  base::TimeDelta start_time;
100  std::string domain_name;
101};
102
103typedef std::vector<ReplayLogEntry> ReplayLog;
104
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.
108//
109// The replay log is a text file where each line contains
110//
111//   timestamp_in_milliseconds domain_name
112//
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.
115//
116// The file should be sorted by timestamp in ascending time.
117bool LoadReplayLog(const base::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());
122    return false;
123  }
124
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
127  // efficiency.
128  std::string replay_log_contents;
129  RemoveChars(original_replay_log_contents, "\r", &replay_log_contents);
130
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 (unsigned i = 0; i < lines.size(); ++i) {
136    if (lines[i].empty())
137      continue;
138    std::vector<std::string> time_and_name;
139    base::SplitString(lines[i], ' ', &time_and_name);
140    if (time_and_name.size() != 2) {
141      fprintf(
142          stderr,
143          "[%s %u] replay log should have format 'timestamp domain_name\\n'\n",
144          file_path.MaybeAsASCII().c_str(),
145          i + 1);
146      bad_parse = true;
147      continue;
148    }
149
150    int64 delta_in_milliseconds;
151    if (!base::StringToInt64(time_and_name[0], &delta_in_milliseconds)) {
152      fprintf(
153          stderr,
154          "[%s %u] replay log should have format 'timestamp domain_name\\n'\n",
155          file_path.MaybeAsASCII().c_str(),
156          i + 1);
157      bad_parse = true;
158      continue;
159    }
160
161    base::TimeDelta delta =
162        base::TimeDelta::FromMilliseconds(delta_in_milliseconds);
163    if (delta < previous_delta) {
164      fprintf(
165          stderr,
166          "[%s %u] replay log should be sorted by time\n",
167          file_path.MaybeAsASCII().c_str(),
168          i + 1);
169      bad_parse = true;
170      continue;
171    }
172
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);
178  }
179  return !bad_parse;
180}
181
182class GDig {
183 public:
184  GDig();
185  ~GDig();
186
187  enum Result {
188    RESULT_NO_RESOLVE = -3,
189    RESULT_NO_CONFIG = -2,
190    RESULT_WRONG_USAGE = -1,
191    RESULT_OK = 0,
192    RESULT_PENDING = 1,
193  };
194
195  Result Main(int argc, const char* argv[]);
196
197 private:
198  bool ParseCommandLine(int argc, const char* argv[]);
199
200  void Start();
201  void Finish(Result);
202
203  void OnDnsConfig(const DnsConfig& dns_config_const);
204  void OnResolveComplete(unsigned index, AddressList* address_list,
205                         base::TimeDelta time_since_start, int val);
206  void OnTimeout();
207  void ReplayNextEntry();
208
209  base::TimeDelta config_timeout_;
210  bool print_config_;
211  bool print_hosts_;
212  net::IPEndPoint nameserver_;
213  base::TimeDelta timeout_;
214  int parallellism_;
215  ReplayLog replay_log_;
216  unsigned replay_log_index_;
217  base::Time start_time_;
218  int active_resolves_;
219  Result result_;
220
221  base::CancelableClosure timeout_closure_;
222  scoped_ptr<DnsConfigService> dns_config_service_;
223  scoped_ptr<FileNetLogObserver> log_observer_;
224  scoped_ptr<NetLog> log_;
225  scoped_ptr<HostResolver> resolver_;
226
227#if defined(OS_MACOSX)
228  // Without this there will be a mem leak on osx.
229  base::mac::ScopedNSAutoreleasePool scoped_pool_;
230#endif
231
232  // Need AtExitManager to support AsWeakPtr (in NetLog).
233  base::AtExitManager exit_manager_;
234};
235
236GDig::GDig()
237    : config_timeout_(base::TimeDelta::FromSeconds(5)),
238      print_config_(false),
239      print_hosts_(false),
240      parallellism_(6),
241      replay_log_index_(0u),
242      active_resolves_(0) {
243}
244
245GDig::~GDig() {
246  if (log_)
247    log_->RemoveThreadSafeObserver(log_observer_.get());
248}
249
250GDig::Result GDig::Main(int argc, const char* argv[]) {
251  if (!ParseCommandLine(argc, argv)) {
252      fprintf(stderr,
253              "usage: %s [--net_log[=<basic|no_bytes|all>]]"
254              " [--print_config] [--print_hosts]"
255              " [--nameserver=<ip_address[:port]>]"
256              " [--timeout=<milliseconds>]"
257              " [--config_timeout=<seconds>]"
258              " [--j=<parallel resolves>]"
259              " [--replay_file=<path>]"
260              " [domain_name]\n",
261              argv[0]);
262      return RESULT_WRONG_USAGE;
263  }
264
265  base::MessageLoopForIO loop;
266
267  result_ = RESULT_PENDING;
268  Start();
269  if (result_ == RESULT_PENDING)
270    base::MessageLoop::current()->Run();
271
272  // Destroy it while MessageLoopForIO is alive.
273  dns_config_service_.reset();
274  return result_;
275}
276
277bool GDig::ParseCommandLine(int argc, const char* argv[]) {
278  CommandLine::Init(argc, argv);
279  const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
280
281  if (parsed_command_line.HasSwitch("config_timeout")) {
282    int timeout_seconds = 0;
283    bool parsed = base::StringToInt(
284        parsed_command_line.GetSwitchValueASCII("config_timeout"),
285        &timeout_seconds);
286    if (parsed && timeout_seconds > 0) {
287      config_timeout_ = base::TimeDelta::FromSeconds(timeout_seconds);
288    } else {
289      fprintf(stderr, "Invalid config_timeout parameter\n");
290      return false;
291    }
292  }
293
294  if (parsed_command_line.HasSwitch("net_log")) {
295    std::string log_param = parsed_command_line.GetSwitchValueASCII("net_log");
296    NetLog::LogLevel level = NetLog::LOG_ALL_BUT_BYTES;
297
298    if (log_param.length() > 0) {
299      std::map<std::string, NetLog::LogLevel> log_levels;
300      log_levels["all"] = NetLog::LOG_ALL;
301      log_levels["no_bytes"] = NetLog::LOG_ALL_BUT_BYTES;
302      log_levels["basic"] = NetLog::LOG_BASIC;
303
304      if (log_levels.find(log_param) != log_levels.end()) {
305        level = log_levels[log_param];
306      } else {
307        fprintf(stderr, "Invalid net_log parameter\n");
308        return false;
309      }
310    }
311    log_.reset(new NetLog);
312    log_observer_.reset(new FileNetLogObserver(stderr));
313    log_->AddThreadSafeObserver(log_observer_.get(), level);
314  }
315
316  print_config_ = parsed_command_line.HasSwitch("print_config");
317  print_hosts_ = parsed_command_line.HasSwitch("print_hosts");
318
319  if (parsed_command_line.HasSwitch("nameserver")) {
320    std::string nameserver =
321      parsed_command_line.GetSwitchValueASCII("nameserver");
322    if (!StringToIPEndPoint(nameserver, &nameserver_)) {
323      fprintf(stderr,
324              "Cannot parse the namerserver string into an IPEndPoint\n");
325      return false;
326    }
327  }
328
329  if (parsed_command_line.HasSwitch("timeout")) {
330    int timeout_millis = 0;
331    bool parsed = base::StringToInt(
332        parsed_command_line.GetSwitchValueASCII("timeout"),
333        &timeout_millis);
334    if (parsed && timeout_millis > 0) {
335      timeout_ = base::TimeDelta::FromMilliseconds(timeout_millis);
336    } else {
337      fprintf(stderr, "Invalid timeout parameter\n");
338      return false;
339    }
340  }
341
342  if (parsed_command_line.HasSwitch("replay_file")) {
343    base::FilePath replay_path =
344        parsed_command_line.GetSwitchValuePath("replay_file");
345    if (!LoadReplayLog(replay_path, &replay_log_))
346      return false;
347  }
348
349  if (parsed_command_line.HasSwitch("j")) {
350    int parallellism = 0;
351    bool parsed = base::StringToInt(
352        parsed_command_line.GetSwitchValueASCII("j"),
353        &parallellism);
354    if (parsed && parallellism > 0) {
355      parallellism_ = parallellism;
356    } else {
357      fprintf(stderr, "Invalid parallellism parameter\n");
358    }
359  }
360
361  if (parsed_command_line.GetArgs().size() == 1) {
362    ReplayLogEntry entry;
363    entry.start_time = base::TimeDelta();
364#if defined(OS_WIN)
365    entry.domain_name = WideToASCII(parsed_command_line.GetArgs()[0]);
366#else
367    entry.domain_name = parsed_command_line.GetArgs()[0];
368#endif
369    replay_log_.push_back(entry);
370  } else if (parsed_command_line.GetArgs().size() != 0) {
371    return false;
372  }
373  return print_config_ || print_hosts_ || !replay_log_.empty();
374}
375
376void GDig::Start() {
377  if (nameserver_.address().size() > 0) {
378    DnsConfig dns_config;
379    dns_config.attempts = 1;
380    dns_config.nameservers.push_back(nameserver_);
381    OnDnsConfig(dns_config);
382  } else {
383    dns_config_service_ = DnsConfigService::CreateSystemService();
384    dns_config_service_->ReadConfig(base::Bind(&GDig::OnDnsConfig,
385                                               base::Unretained(this)));
386    timeout_closure_.Reset(base::Bind(&GDig::OnTimeout,
387                                      base::Unretained(this)));
388    base::MessageLoop::current()->PostDelayedTask(
389        FROM_HERE, timeout_closure_.callback(), config_timeout_);
390  }
391}
392
393void GDig::Finish(Result result) {
394  DCHECK_NE(RESULT_PENDING, result);
395  result_ = result;
396  if (base::MessageLoop::current())
397    base::MessageLoop::current()->Quit();
398}
399
400void GDig::OnDnsConfig(const DnsConfig& dns_config_const) {
401  timeout_closure_.Cancel();
402  DCHECK(dns_config_const.IsValid());
403  DnsConfig dns_config = dns_config_const;
404
405  if (timeout_.InMilliseconds() > 0)
406    dns_config.timeout = timeout_;
407  if (print_config_) {
408    printf("# Dns Configuration\n"
409           "%s", DnsConfigToString(dns_config).c_str());
410  }
411  if (print_hosts_) {
412    printf("# Host Database\n"
413           "%s", DnsHostsToString(dns_config.hosts).c_str());
414  }
415
416  if (replay_log_.empty()) {
417    Finish(RESULT_OK);
418    return;
419  }
420
421  scoped_ptr<DnsClient> dns_client(DnsClient::CreateClient(NULL));
422  dns_client->SetConfig(dns_config);
423  scoped_ptr<HostResolverImpl> resolver(
424      new HostResolverImpl(
425          HostCache::CreateDefaultCache(),
426          PrioritizedDispatcher::Limits(NUM_PRIORITIES, parallellism_),
427          HostResolverImpl::ProcTaskParams(NULL, 1),
428          log_.get()));
429  resolver->SetDnsClient(dns_client.Pass());
430  resolver_ = resolver.Pass();
431
432  start_time_ = base::Time::Now();
433
434  ReplayNextEntry();
435}
436
437void 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, addrlist, callback, NULL,
464        BoundNetLog::Make(log_.get(), net::NetLog::SOURCE_NONE));
465    if (ret != ERR_IO_PENDING)
466      callback.Run(ret);
467  }
468}
469
470void GDig::OnResolveComplete(unsigned entry_index,
471                             AddressList* address_list,
472                             base::TimeDelta resolve_start_time,
473                             int val) {
474  DCHECK_GT(active_resolves_, 0);
475  DCHECK(address_list);
476  DCHECK_LT(entry_index, replay_log_.size());
477  --active_resolves_;
478  base::TimeDelta resolve_end_time = base::Time::Now() - start_time_;
479  base::TimeDelta resolve_time = resolve_end_time - resolve_start_time;
480  printf("%u %d %d %s %d ",
481         entry_index,
482         static_cast<int>(resolve_end_time.InMilliseconds()),
483         static_cast<int>(resolve_time.InMilliseconds()),
484         replay_log_[entry_index].domain_name.c_str(), val);
485  if (val != OK) {
486    printf("%s", ErrorToString(val));
487  } else {
488    for (size_t i = 0; i < address_list->size(); ++i) {
489      if (i != 0)
490        printf(" ");
491      printf("%s", (*address_list)[i].ToStringWithoutPort().c_str());
492    }
493  }
494  printf("\n");
495  if (active_resolves_ == 0 && replay_log_index_ >= replay_log_.size())
496    Finish(RESULT_OK);
497}
498
499void GDig::OnTimeout() {
500  fprintf(stderr, "Timed out waiting to load the dns config\n");
501  Finish(RESULT_NO_CONFIG);
502}
503
504}  // empty namespace
505
506}  // namespace net
507
508int main(int argc, const char* argv[]) {
509  net::GDig dig;
510  return dig.Main(argc, argv);
511}
512