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