Home | History | Annotate | Download | only in gdig
      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 
     36 namespace net {
     37 
     38 namespace {
     39 
     40 bool 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.
     60 std::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.
     85 std::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 
     98 struct ReplayLogEntry {
     99   base::TimeDelta start_time;
    100   std::string domain_name;
    101 };
    102 
    103 typedef 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.
    117 bool 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 
    182 class 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 
    236 GDig::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 
    245 GDig::~GDig() {
    246   if (log_)
    247     log_->RemoveThreadSafeObserver(log_observer_.get());
    248 }
    249 
    250 GDig::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 
    277 bool 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 
    376 void 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 
    393 void 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 
    400 void 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 
    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, 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 
    470 void 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 
    499 void 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 
    508 int main(int argc, const char* argv[]) {
    509   net::GDig dig;
    510   return dig.Main(argc, argv);
    511 }
    512