// Copyright (c) 2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // hrseolv is a command line utility which runs the HostResolver in the // Chromium network stack. // // The user specifies the hosts to lookup and when to look them up. // The hosts must be specified in order. // The hosts can be contained in a file or on the command line. If no // time is specified, the resolv is assumed to be the same time as the // previous host - which is an offset of 0 for the very first host. // // The user can also control whether the lookups happen asynchronously // or synchronously by specifying --async on the command line. // // Future ideas: // Specify whether the lookup is speculative. // Interleave synchronous and asynchronous lookups. // Specify the address family. #include <stdio.h> #include <string> #include "base/at_exit.h" #include "base/command_line.h" #include "base/condition_variable.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/string_util.h" #include "base/thread.h" #include "base/time.h" #include "base/waitable_event.h" #include "net/base/address_list.h" #include "net/base/completion_callback.h" #include "net/base/host_resolver_impl.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" #include "net/base/sys_addrinfo.h" struct FlagName { int flag; const char* name; }; static const FlagName kAddrinfoFlagNames[] = { {AI_PASSIVE, "AI_PASSIVE"}, {AI_CANONNAME, "AI_CANONNAME"}, {AI_NUMERICHOST, "AI_NUMERICHOST"}, {AI_V4MAPPED, "AI_V4MAPPED"}, {AI_ALL, "AI_ALL"}, {AI_ADDRCONFIG, "AI_ADDRCONFIG"}, #if defined(OS_LINUX) || defined(OS_WIN) {AI_NUMERICSERV, "AI_NUMERICSERV"}, #endif }; std::string FormatAddrinfoFlags(int ai_flags) { std::string flag_names; for (unsigned int i = 0; i < arraysize(kAddrinfoFlagNames); ++i) { const FlagName& flag_name = kAddrinfoFlagNames[i]; if (ai_flags & flag_name.flag) { ai_flags &= ~flag_name.flag; if (!flag_names.empty()) { flag_names += "|"; } flag_names += flag_name.name; } } if (ai_flags) { if (!flag_names.empty()) { flag_names += "|"; } StringAppendF(&flag_names, "0x%x", ai_flags); } return flag_names; } const char* GetNameOfFlag(const FlagName* flag_names, unsigned int num_flag_names, int flag) { for (unsigned int i = 0; i < num_flag_names; ++i) { const FlagName& flag_name = flag_names[i]; if (flag_name.flag == flag) { return flag_name.name; } } return "UNKNOWN"; } static const FlagName kFamilyFlagNames[] = { {AF_UNSPEC, "AF_UNSPEC"}, {AF_INET, "AF_INET"}, {AF_INET6, "AF_INET6"}, }; const char* FormatAddrinfoFamily(int ai_family) { return GetNameOfFlag(kFamilyFlagNames, arraysize(kFamilyFlagNames), ai_family); } static const FlagName kSocktypeFlagNames[] = { {SOCK_STREAM, "SOCK_STREAM"}, {SOCK_DGRAM, "SOCK_DGRAM"}, {SOCK_RAW, "SOCK_RAW"}, }; const char* FormatAddrinfoSocktype(int ai_socktype) { return GetNameOfFlag(kSocktypeFlagNames, arraysize(kSocktypeFlagNames), ai_socktype); } static const FlagName kProtocolFlagNames[] = { {IPPROTO_TCP, "IPPROTO_TCP"}, {IPPROTO_UDP, "IPPROTO_UDP"}, }; const char* FormatAddrinfoProtocol(int ai_protocol) { return GetNameOfFlag(kProtocolFlagNames, arraysize(kProtocolFlagNames), ai_protocol); } std::string FormatAddrinfoDetails(const struct addrinfo& ai, const char* indent) { std::string ai_flags = FormatAddrinfoFlags(ai.ai_flags); const char* ai_family = FormatAddrinfoFamily(ai.ai_family); const char* ai_socktype = FormatAddrinfoSocktype(ai.ai_socktype); const char* ai_protocol = FormatAddrinfoProtocol(ai.ai_protocol); std::string ai_addr = net::NetAddressToString(&ai); std::string ai_canonname; if (ai.ai_canonname) { ai_canonname = StringPrintf("%s ai_canonname: %s\n", indent, ai.ai_canonname); } return StringPrintf("%saddrinfo {\n" "%s ai_flags: %s\n" "%s ai_family: %s\n" "%s ai_socktype: %s\n" "%s ai_protocol: %s\n" "%s ai_addrlen: %d\n" "%s ai_addr: %s\n" "%s" "%s}\n", indent, indent, ai_flags.c_str(), indent, ai_family, indent, ai_socktype, indent, ai_protocol, indent, ai.ai_addrlen, indent, ai_addr.c_str(), ai_canonname.c_str(), indent); } std::string FormatAddressList(const net::AddressList& address_list, const std::string& host) { std::string ret_string; StringAppendF(&ret_string, "AddressList {\n"); StringAppendF(&ret_string, " Host: %s\n", host.c_str()); for (const struct addrinfo* it = address_list.head(); it != NULL; it = it->ai_next) { StringAppendF(&ret_string, "%s", FormatAddrinfoDetails(*it, " ").c_str()); } StringAppendF(&ret_string, "}\n"); return ret_string; } class ResolverInvoker; // DelayedResolve contains state for a DNS resolution to be performed later. class DelayedResolve : public base::RefCounted<DelayedResolve> { public: DelayedResolve(const std::string& host, bool is_async, net::HostResolver* resolver, ResolverInvoker* invoker) : host_(host), address_list_(), is_async_(is_async), resolver_(resolver), invoker_(invoker), ALLOW_THIS_IN_INITIALIZER_LIST( io_callback_(this, &DelayedResolve::OnResolveComplete)) { } void Start() { net::CompletionCallback* callback = (is_async_) ? &io_callback_ : NULL; net::HostResolver::RequestInfo request_info(host_, 80); int rv = resolver_->Resolve(request_info, &address_list_, callback, NULL, NULL); if (rv != net::ERR_IO_PENDING) { OnResolveComplete(rv); } } private: // Without this, VC++ complains about the private destructor below. friend class base::RefCounted<DelayedResolve>; // The destructor is called by Release. ~DelayedResolve() {} void OnResolveComplete(int result); std::string host_; net::AddressList address_list_; bool is_async_; scoped_refptr<net::HostResolver> resolver_; ResolverInvoker* invoker_; net::CompletionCallbackImpl<DelayedResolve> io_callback_; }; struct HostAndTime { // The host to resolve, i.e. www.google.com std::string host; // Time since the start of this program to actually kick off the resolution. int delta_in_milliseconds; }; // Invokes a sequence of host resolutions at specified times. class ResolverInvoker { public: explicit ResolverInvoker(net::HostResolver* resolver) : message_loop_(MessageLoop::TYPE_DEFAULT), resolver_(resolver), remaining_requests_(0) { } ~ResolverInvoker() { } // Resolves all specified hosts in the order provided. hosts_and_times is // assumed to be ordered by the delta_in_milliseconds field. There is no // guarantee that the resolutions will complete in the order specified when // async is true. There is no guarantee that the DNS queries will be issued // at exactly the time specified by delta_in_milliseconds, but they are // guaranteed to be issued at a time >= delta_in_milliseconds. // // When async is true, HostResolver::Resolve will issue the DNS lookups // asynchronously - this can be used to have multiple requests in flight at // the same time. // // ResolveAll will block until all resolutions are complete. void ResolveAll(const std::vector<HostAndTime>& hosts_and_times, bool async) { // Schedule all tasks on our message loop, and then run. int num_requests = hosts_and_times.size(); remaining_requests_ = num_requests; for (int i = 0; i < num_requests; ++i) { const HostAndTime& host_and_time = hosts_and_times[i]; const std::string& host = host_and_time.host; DelayedResolve* resolve_request = new DelayedResolve(host, async, resolver_, this); resolve_request->AddRef(); message_loop_.PostDelayedTask( FROM_HERE, NewRunnableMethod(resolve_request, &DelayedResolve::Start), host_and_time.delta_in_milliseconds); } message_loop_.Run(); } private: friend class DelayedResolve; void OnResolved(int err, net::AddressList* address_list, const std::string& host) { if (err == net::OK) { printf("%s", FormatAddressList(*address_list, host).c_str()); } else { printf("Error resolving %s\n", host.c_str()); } DCHECK(remaining_requests_ > 0); --remaining_requests_; if (remaining_requests_ == 0) { message_loop_.Quit(); } } MessageLoop message_loop_; scoped_refptr<net::HostResolver> resolver_; int remaining_requests_; }; void DelayedResolve::OnResolveComplete(int result) { invoker_->OnResolved(result, &address_list_, host_); this->Release(); } struct CommandLineOptions { CommandLineOptions() : verbose(false), async(false), cache_size(100), cache_ttl(50), input_path() { } bool verbose; bool async; int cache_size; int cache_ttl; FilePath input_path; }; const char* kAsync = "async"; const char* kCacheSize = "cache-size"; const char* kCacheTtl = "cache-ttl"; const char* kInputPath = "input-path"; // Parses the command line values. Returns false if there is a problem, // options otherwise. bool ParseCommandLine(CommandLine* command_line, CommandLineOptions* options) { options->async = command_line->HasSwitch(kAsync); if (command_line->HasSwitch(kCacheSize)) { std::string cache_size = command_line->GetSwitchValueASCII(kCacheSize); bool valid_size = StringToInt(cache_size, &options->cache_size); if (valid_size) { valid_size = options->cache_size >= 0; } if (!valid_size) { printf("Invalid --cachesize value: %s\n", cache_size.c_str()); return false; } } if (command_line->HasSwitch(kCacheTtl)) { std::string cache_ttl = command_line->GetSwitchValueASCII(kCacheTtl); bool valid_ttl = StringToInt(cache_ttl, &options->cache_ttl); if (valid_ttl) { valid_ttl = options->cache_ttl >= 0; } if (!valid_ttl) { printf("Invalid --cachettl value: %s\n", cache_ttl.c_str()); return false; } } if (command_line->HasSwitch(kInputPath)) { options->input_path = command_line->GetSwitchValuePath(kInputPath); } return true; } bool ReadHostsAndTimesFromLooseValues( const std::vector<std::wstring>& loose_args, std::vector<HostAndTime>* hosts_and_times) { std::vector<std::wstring>::const_iterator loose_args_end = loose_args.end(); for (std::vector<std::wstring>::const_iterator it = loose_args.begin(); it != loose_args_end; ++it) { // TODO(cbentzel): Read time offset. HostAndTime host_and_time = {WideToASCII(*it), 0}; hosts_and_times->push_back(host_and_time); } return true; } bool ReadHostsAndTimesFromFile(const FilePath& path, std::vector<HostAndTime>* hosts_and_times) { // TODO(cbentzel): There are smarter and safer ways to do this. std::string file_contents; if (!file_util::ReadFileToString(path, &file_contents)) { return false; } std::vector<std::string> lines; // TODO(cbentzel): This should probably handle CRLF-style separators as well. // Maybe it's worth adding functionality like this to base tools. SplitString(file_contents, '\n', &lines); std::vector<std::string>::const_iterator line_end = lines.end(); int previous_timestamp = 0; for (std::vector<std::string>::const_iterator it = lines.begin(); it != line_end; ++it) { std::vector<std::string> tokens; SplitStringAlongWhitespace(*it, &tokens); switch (tokens.size()) { case 0: // Unexpected, but keep going. break; case 1: { HostAndTime host_and_time = {tokens[0], previous_timestamp}; hosts_and_times->push_back(host_and_time); break; } case 2: { int timestamp; if (!StringToInt(tokens[1], ×tamp)) { // Unexpected value - keep going. } if (timestamp < previous_timestamp) { // Unexpected value - ignore. } previous_timestamp = timestamp; HostAndTime host_and_time = {tokens[0], timestamp}; hosts_and_times->push_back(host_and_time); break; } default: DCHECK(false); break; } } return true; } int main(int argc, char** argv) { base::AtExitManager at_exit_manager; CommandLine::Init(argc, argv); CommandLine* command_line = CommandLine::ForCurrentProcess(); CommandLineOptions options; if (!ParseCommandLine(command_line, &options)) { exit(1); } // Get the hosts and times - either from a file or command line options. // TODO(cbentzel): Add stdin support to POSIX versions - not sure if // there's an equivalent in Windows. // TODO(cbentzel): If really large, may not want to spool the whole // file into memory. std::vector<HostAndTime> hosts_and_times; if (options.input_path.empty()) { if (!ReadHostsAndTimesFromLooseValues(command_line->GetLooseValues(), &hosts_and_times)) { exit(1); } } else { if (!ReadHostsAndTimesFromFile(options.input_path, &hosts_and_times)) { exit(1); } } net::HostCache* cache = new net::HostCache( options.cache_size, base::TimeDelta::FromMilliseconds(options.cache_ttl), base::TimeDelta::FromSeconds(0)); scoped_refptr<net::HostResolver> host_resolver( new net::HostResolverImpl(NULL, cache, NULL, 100u)); ResolverInvoker invoker(host_resolver.get()); invoker.ResolveAll(hosts_and_times, options.async); CommandLine::Reset(); return 0; }