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], ×tamp)) { 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