Home | History | Annotate | Download | only in hresolv
      1 // Copyright (c) 2009 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 // hrseolv is a command line utility which runs the HostResolver in the
      6 // Chromium network stack.
      7 //
      8 // The user specifies the hosts to lookup and when to look them up.
      9 // The hosts must be specified in order.
     10 // The hosts can be contained in a file or on the command line. If no
     11 // time is specified, the resolv is assumed to be the same time as the
     12 // previous host - which is an offset of 0 for the very first host.
     13 //
     14 // The user can also control whether the lookups happen asynchronously
     15 // or synchronously by specifying --async on the command line.
     16 //
     17 // Future ideas:
     18 //   Specify whether the lookup is speculative.
     19 //   Interleave synchronous and asynchronous lookups.
     20 //   Specify the address family.
     21 
     22 #include <stdio.h>
     23 #include <string>
     24 
     25 #include "base/at_exit.h"
     26 #include "base/command_line.h"
     27 #include "base/condition_variable.h"
     28 #include "base/file_path.h"
     29 #include "base/file_util.h"
     30 #include "base/string_util.h"
     31 #include "base/thread.h"
     32 #include "base/time.h"
     33 #include "base/waitable_event.h"
     34 #include "net/base/address_list.h"
     35 #include "net/base/completion_callback.h"
     36 #include "net/base/host_resolver_impl.h"
     37 #include "net/base/net_errors.h"
     38 #include "net/base/net_util.h"
     39 #include "net/base/sys_addrinfo.h"
     40 
     41 struct FlagName {
     42   int flag;
     43   const char* name;
     44 };
     45 
     46 static const FlagName kAddrinfoFlagNames[] = {
     47   {AI_PASSIVE, "AI_PASSIVE"},
     48   {AI_CANONNAME, "AI_CANONNAME"},
     49   {AI_NUMERICHOST, "AI_NUMERICHOST"},
     50   {AI_V4MAPPED, "AI_V4MAPPED"},
     51   {AI_ALL, "AI_ALL"},
     52   {AI_ADDRCONFIG, "AI_ADDRCONFIG"},
     53 #if defined(OS_LINUX) || defined(OS_WIN)
     54   {AI_NUMERICSERV, "AI_NUMERICSERV"},
     55 #endif
     56 };
     57 
     58 std::string FormatAddrinfoFlags(int ai_flags) {
     59   std::string flag_names;
     60   for (unsigned int i = 0; i < arraysize(kAddrinfoFlagNames); ++i) {
     61     const FlagName& flag_name = kAddrinfoFlagNames[i];
     62     if (ai_flags & flag_name.flag) {
     63       ai_flags &= ~flag_name.flag;
     64       if (!flag_names.empty()) {
     65         flag_names += "|";
     66       }
     67       flag_names += flag_name.name;
     68     }
     69   }
     70   if (ai_flags) {
     71     if (!flag_names.empty()) {
     72       flag_names += "|";
     73     }
     74     StringAppendF(&flag_names, "0x%x", ai_flags);
     75   }
     76   return flag_names;
     77 }
     78 
     79 const char* GetNameOfFlag(const FlagName* flag_names,
     80                           unsigned int num_flag_names,
     81                           int flag) {
     82   for (unsigned int i = 0; i < num_flag_names; ++i) {
     83     const FlagName& flag_name = flag_names[i];
     84     if (flag_name.flag == flag) {
     85       return flag_name.name;
     86     }
     87   }
     88   return "UNKNOWN";
     89 }
     90 
     91 static const FlagName kFamilyFlagNames[] = {
     92   {AF_UNSPEC, "AF_UNSPEC"},
     93   {AF_INET, "AF_INET"},
     94   {AF_INET6, "AF_INET6"},
     95 };
     96 
     97 const char* FormatAddrinfoFamily(int ai_family) {
     98   return GetNameOfFlag(kFamilyFlagNames,
     99                        arraysize(kFamilyFlagNames),
    100                        ai_family);
    101 }
    102 
    103 static const FlagName kSocktypeFlagNames[] = {
    104   {SOCK_STREAM, "SOCK_STREAM"},
    105   {SOCK_DGRAM, "SOCK_DGRAM"},
    106   {SOCK_RAW, "SOCK_RAW"},
    107 };
    108 
    109 const char* FormatAddrinfoSocktype(int ai_socktype) {
    110   return GetNameOfFlag(kSocktypeFlagNames,
    111                        arraysize(kSocktypeFlagNames),
    112                        ai_socktype);
    113 }
    114 
    115 static const FlagName kProtocolFlagNames[] = {
    116   {IPPROTO_TCP, "IPPROTO_TCP"},
    117   {IPPROTO_UDP, "IPPROTO_UDP"},
    118 };
    119 
    120 const char* FormatAddrinfoProtocol(int ai_protocol) {
    121   return GetNameOfFlag(kProtocolFlagNames,
    122                        arraysize(kProtocolFlagNames),
    123                        ai_protocol);
    124 }
    125 
    126 std::string FormatAddrinfoDetails(const struct addrinfo& ai,
    127                                   const char* indent) {
    128   std::string ai_flags = FormatAddrinfoFlags(ai.ai_flags);
    129   const char* ai_family = FormatAddrinfoFamily(ai.ai_family);
    130   const char* ai_socktype = FormatAddrinfoSocktype(ai.ai_socktype);
    131   const char* ai_protocol = FormatAddrinfoProtocol(ai.ai_protocol);
    132   std::string ai_addr = net::NetAddressToString(&ai);
    133   std::string ai_canonname;
    134   if (ai.ai_canonname) {
    135     ai_canonname = StringPrintf("%s  ai_canonname: %s\n",
    136                                 indent,
    137                                 ai.ai_canonname);
    138   }
    139   return StringPrintf("%saddrinfo {\n"
    140                       "%s  ai_flags: %s\n"
    141                       "%s  ai_family: %s\n"
    142                       "%s  ai_socktype: %s\n"
    143                       "%s  ai_protocol: %s\n"
    144                       "%s  ai_addrlen: %d\n"
    145                       "%s  ai_addr: %s\n"
    146                       "%s"
    147                       "%s}\n",
    148                       indent,
    149                       indent, ai_flags.c_str(),
    150                       indent, ai_family,
    151                       indent, ai_socktype,
    152                       indent, ai_protocol,
    153                       indent, ai.ai_addrlen,
    154                       indent, ai_addr.c_str(),
    155                       ai_canonname.c_str(),
    156                       indent);
    157 }
    158 
    159 std::string FormatAddressList(const net::AddressList& address_list,
    160                               const std::string& host) {
    161   std::string ret_string;
    162   StringAppendF(&ret_string, "AddressList {\n");
    163   StringAppendF(&ret_string, "  Host: %s\n", host.c_str());
    164   for (const struct addrinfo* it = address_list.head();
    165        it != NULL;
    166        it = it->ai_next) {
    167     StringAppendF(&ret_string, "%s", FormatAddrinfoDetails(*it, "  ").c_str());
    168   }
    169   StringAppendF(&ret_string, "}\n");
    170   return ret_string;
    171 }
    172 
    173 class ResolverInvoker;
    174 
    175 // DelayedResolve contains state for a DNS resolution to be performed later.
    176 class DelayedResolve : public base::RefCounted<DelayedResolve> {
    177  public:
    178   DelayedResolve(const std::string& host, bool is_async,
    179                  net::HostResolver* resolver,
    180                  ResolverInvoker* invoker)
    181       : host_(host),
    182         address_list_(),
    183         is_async_(is_async),
    184         resolver_(resolver),
    185         invoker_(invoker),
    186         ALLOW_THIS_IN_INITIALIZER_LIST(
    187             io_callback_(this, &DelayedResolve::OnResolveComplete)) {
    188    }
    189 
    190   void Start() {
    191     net::CompletionCallback* callback = (is_async_) ? &io_callback_ : NULL;
    192     net::HostResolver::RequestInfo request_info(host_, 80);
    193     int rv = resolver_->Resolve(request_info,
    194                                 &address_list_,
    195                                 callback,
    196                                 NULL,
    197                                 NULL);
    198     if (rv != net::ERR_IO_PENDING) {
    199       OnResolveComplete(rv);
    200     }
    201   }
    202 
    203  private:
    204 
    205   // Without this, VC++ complains about the private destructor below.
    206   friend class base::RefCounted<DelayedResolve>;
    207 
    208   // The destructor is called by Release.
    209   ~DelayedResolve() {}
    210 
    211   void OnResolveComplete(int result);
    212 
    213   std::string host_;
    214   net::AddressList address_list_;
    215   bool is_async_;
    216   scoped_refptr<net::HostResolver> resolver_;
    217   ResolverInvoker* invoker_;
    218   net::CompletionCallbackImpl<DelayedResolve> io_callback_;
    219 };
    220 
    221 
    222 struct HostAndTime {
    223   // The host to resolve, i.e. www.google.com
    224   std::string host;
    225   // Time since the start of this program to actually kick off the resolution.
    226   int delta_in_milliseconds;
    227 };
    228 
    229 // Invokes a sequence of host resolutions at specified times.
    230 class ResolverInvoker {
    231  public:
    232   explicit ResolverInvoker(net::HostResolver* resolver)
    233       : message_loop_(MessageLoop::TYPE_DEFAULT),
    234         resolver_(resolver),
    235         remaining_requests_(0) {
    236   }
    237 
    238   ~ResolverInvoker() {
    239   }
    240 
    241   // Resolves all specified hosts in the order provided. hosts_and_times is
    242   // assumed to be ordered by the delta_in_milliseconds field. There is no
    243   // guarantee that the resolutions will complete in the order specified when
    244   // async is true. There is no guarantee that the DNS queries will be issued
    245   // at exactly the time specified by delta_in_milliseconds, but they are
    246   // guaranteed to be issued at a time >= delta_in_milliseconds.
    247   //
    248   // When async is true, HostResolver::Resolve will issue the DNS lookups
    249   // asynchronously - this can be used to have multiple requests in flight at
    250   // the same time.
    251   //
    252   // ResolveAll will block until all resolutions are complete.
    253   void ResolveAll(const std::vector<HostAndTime>& hosts_and_times,
    254                   bool async) {
    255     // Schedule all tasks on our message loop, and then run.
    256     int num_requests = hosts_and_times.size();
    257     remaining_requests_ = num_requests;
    258     for (int i = 0; i < num_requests; ++i) {
    259       const HostAndTime& host_and_time = hosts_and_times[i];
    260       const std::string& host = host_and_time.host;
    261       DelayedResolve* resolve_request = new DelayedResolve(host,
    262         async, resolver_, this);
    263       resolve_request->AddRef();
    264       message_loop_.PostDelayedTask(
    265           FROM_HERE,
    266           NewRunnableMethod(resolve_request, &DelayedResolve::Start),
    267           host_and_time.delta_in_milliseconds);
    268     }
    269     message_loop_.Run();
    270   }
    271 
    272  private:
    273   friend class DelayedResolve;
    274 
    275   void OnResolved(int err, net::AddressList* address_list,
    276                   const std::string& host) {
    277     if (err == net::OK) {
    278       printf("%s", FormatAddressList(*address_list, host).c_str());
    279     } else {
    280       printf("Error resolving %s\n", host.c_str());
    281     }
    282     DCHECK(remaining_requests_ > 0);
    283     --remaining_requests_;
    284     if (remaining_requests_ == 0) {
    285       message_loop_.Quit();
    286     }
    287   }
    288 
    289   MessageLoop message_loop_;
    290   scoped_refptr<net::HostResolver> resolver_;
    291   int remaining_requests_;
    292 };
    293 
    294 void DelayedResolve::OnResolveComplete(int result) {
    295   invoker_->OnResolved(result, &address_list_, host_);
    296   this->Release();
    297 }
    298 
    299 struct CommandLineOptions {
    300   CommandLineOptions()
    301       : verbose(false),
    302         async(false),
    303         cache_size(100),
    304         cache_ttl(50),
    305         input_path() {
    306   }
    307 
    308   bool verbose;
    309   bool async;
    310   int cache_size;
    311   int cache_ttl;
    312   FilePath input_path;
    313 };
    314 
    315 const char* kAsync = "async";
    316 const char* kCacheSize = "cache-size";
    317 const char* kCacheTtl = "cache-ttl";
    318 const char* kInputPath = "input-path";
    319 
    320 // Parses the command line values. Returns false if there is a problem,
    321 // options otherwise.
    322 bool ParseCommandLine(CommandLine* command_line, CommandLineOptions* options) {
    323   options->async = command_line->HasSwitch(kAsync);
    324   if (command_line->HasSwitch(kCacheSize)) {
    325     std::string cache_size = command_line->GetSwitchValueASCII(kCacheSize);
    326     bool valid_size = StringToInt(cache_size, &options->cache_size);
    327     if (valid_size) {
    328       valid_size = options->cache_size >= 0;
    329     }
    330     if (!valid_size) {
    331       printf("Invalid --cachesize value: %s\n", cache_size.c_str());
    332       return false;
    333     }
    334   }
    335 
    336   if (command_line->HasSwitch(kCacheTtl)) {
    337     std::string cache_ttl = command_line->GetSwitchValueASCII(kCacheTtl);
    338     bool valid_ttl = StringToInt(cache_ttl, &options->cache_ttl);
    339     if (valid_ttl) {
    340       valid_ttl = options->cache_ttl >= 0;
    341     }
    342     if (!valid_ttl) {
    343       printf("Invalid --cachettl value: %s\n", cache_ttl.c_str());
    344       return false;
    345     }
    346   }
    347 
    348   if (command_line->HasSwitch(kInputPath)) {
    349     options->input_path = command_line->GetSwitchValuePath(kInputPath);
    350   }
    351 
    352   return true;
    353 }
    354 
    355 bool ReadHostsAndTimesFromLooseValues(
    356     const std::vector<std::wstring>& loose_args,
    357     std::vector<HostAndTime>* hosts_and_times) {
    358   std::vector<std::wstring>::const_iterator loose_args_end = loose_args.end();
    359   for (std::vector<std::wstring>::const_iterator it = loose_args.begin();
    360        it != loose_args_end;
    361        ++it) {
    362     // TODO(cbentzel): Read time offset.
    363     HostAndTime host_and_time = {WideToASCII(*it), 0};
    364     hosts_and_times->push_back(host_and_time);
    365   }
    366   return true;
    367 }
    368 
    369 bool ReadHostsAndTimesFromFile(const FilePath& path,
    370                                std::vector<HostAndTime>* hosts_and_times) {
    371   // TODO(cbentzel): There are smarter and safer ways to do this.
    372   std::string file_contents;
    373   if (!file_util::ReadFileToString(path, &file_contents)) {
    374     return false;
    375   }
    376   std::vector<std::string> lines;
    377   // TODO(cbentzel): This should probably handle CRLF-style separators as well.
    378   // Maybe it's worth adding functionality like this to base tools.
    379   SplitString(file_contents, '\n', &lines);
    380   std::vector<std::string>::const_iterator line_end = lines.end();
    381   int previous_timestamp = 0;
    382   for (std::vector<std::string>::const_iterator it = lines.begin();
    383        it != line_end;
    384        ++it) {
    385     std::vector<std::string> tokens;
    386     SplitStringAlongWhitespace(*it, &tokens);
    387     switch (tokens.size()) {
    388       case 0:
    389         // Unexpected, but keep going.
    390         break;
    391       case 1: {
    392         HostAndTime host_and_time = {tokens[0], previous_timestamp};
    393         hosts_and_times->push_back(host_and_time);
    394         break;
    395       }
    396       case 2: {
    397         int timestamp;
    398         if (!StringToInt(tokens[1], &timestamp)) {
    399           // Unexpected value - keep going.
    400         }
    401         if (timestamp < previous_timestamp) {
    402           // Unexpected value - ignore.
    403         }
    404         previous_timestamp = timestamp;
    405         HostAndTime host_and_time = {tokens[0], timestamp};
    406         hosts_and_times->push_back(host_and_time);
    407         break;
    408       }
    409       default:
    410         DCHECK(false);
    411         break;
    412     }
    413   }
    414   return true;
    415 }
    416 
    417 int main(int argc, char** argv) {
    418   base::AtExitManager at_exit_manager;
    419   CommandLine::Init(argc, argv);
    420   CommandLine* command_line = CommandLine::ForCurrentProcess();
    421   CommandLineOptions options;
    422   if (!ParseCommandLine(command_line, &options)) {
    423     exit(1);
    424   }
    425 
    426   // Get the hosts and times - either from a file or command line options.
    427   // TODO(cbentzel): Add stdin support to POSIX versions - not sure if
    428   // there's an equivalent in Windows.
    429   // TODO(cbentzel): If really large, may not want to spool the whole
    430   // file into memory.
    431   std::vector<HostAndTime> hosts_and_times;
    432   if (options.input_path.empty()) {
    433     if (!ReadHostsAndTimesFromLooseValues(command_line->GetLooseValues(),
    434                                           &hosts_and_times)) {
    435       exit(1);
    436     }
    437   } else {
    438     if (!ReadHostsAndTimesFromFile(options.input_path, &hosts_and_times)) {
    439       exit(1);
    440     }
    441   }
    442 
    443   net::HostCache* cache = new net::HostCache(
    444       options.cache_size,
    445       base::TimeDelta::FromMilliseconds(options.cache_ttl),
    446       base::TimeDelta::FromSeconds(0));
    447 
    448   scoped_refptr<net::HostResolver> host_resolver(
    449       new net::HostResolverImpl(NULL, cache, NULL, 100u));
    450   ResolverInvoker invoker(host_resolver.get());
    451   invoker.ResolveAll(hosts_and_times, options.async);
    452 
    453   CommandLine::Reset();
    454   return 0;
    455 }
    456