diff options
author | eroman@chromium.org <eroman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-10 02:38:03 +0000 |
---|---|---|
committer | eroman@chromium.org <eroman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-10 02:38:03 +0000 |
commit | c402da4e582bb1b5133270a2077104e752fc9704 (patch) | |
tree | b25691fad942949f281500f6d98a7d4c4e1478c1 | |
parent | 07ec104f30de62ac6114636eddb231df59021d34 (diff) | |
download | chromium_src-c402da4e582bb1b5133270a2077104e752fc9704.zip chromium_src-c402da4e582bb1b5133270a2077104e752fc9704.tar.gz chromium_src-c402da4e582bb1b5133270a2077104e752fc9704.tar.bz2 |
Original patch by cbentzel@google.com
Original code review: http://codereview.chromium.org/428004
Command line utility to run the HostResolver.
The user provides a list of hosts to resolve, as well as optional timestamps
for when the resolutions should happen. These can be provided on the command
line as additional arguments or in a specified file.
Options:
--async: Specifies that the resolution should happen asynchronously.
--verbose: Spew out extra data.
--cachesize: Size of the DNS cache.
--cachettl: TTL of DNS cache entries in milliseconds.
--inputpath: File containing host name and optional timestamp, one per line.
BUG=NONE
TEST=Built and ran on Linux and Windows. Built on OSX via trybots but not run.
R=eroman
Review URL: http://codereview.chromium.org/485006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@34225 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | net/net.gyp | 11 | ||||
-rw-r--r-- | net/tools/hresolv/hresolv.cc | 456 |
2 files changed, 467 insertions, 0 deletions
diff --git a/net/net.gyp b/net/net.gyp index b0672cf..4f50133 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -849,6 +849,17 @@ 'tools/fetch/http_session.h', ], }, + { + 'target_name': 'hresolv', + 'type': 'executable', + 'dependencies': [ + 'net_base', + ], + 'msvs_guid': 'FF1BAC48-D326-4CB4-96DA-8B03DE23ED6E', + 'sources': [ + 'tools/hresolv/hresolv.cc', + ], + }, ], 'conditions': [ ['OS=="win"', { diff --git a/net/tools/hresolv/hresolv.cc b/net/tools/hresolv/hresolv.cc new file mode 100644 index 0000000..18a98ff --- /dev/null +++ b/net/tools/hresolv/hresolv.cc @@ -0,0 +1,456 @@ +// 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 "build/build_config.h" + +#if defined(OS_WIN) +#include <ws2tcpip.h> +#else +#include <netdb.h> +#endif +#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" + +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 += "|"; + } + flag_names += StringPrintf("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); + } + } + + scoped_refptr<net::HostResolver> host_resolver( + new net::HostResolverImpl(NULL, options.cache_size, options.cache_ttl)); + ResolverInvoker invoker(host_resolver.get()); + invoker.ResolveAll(hosts_and_times, options.async); + + CommandLine::Reset(); + return 0; +} |