1// Copyright (c) 2012 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#include <stdio.h> 6#include <string> 7 8#include "base/at_exit.h" 9#include "base/bind.h" 10#include "base/cancelable_callback.h" 11#include "base/command_line.h" 12#include "base/file_util.h" 13#include "base/memory/scoped_ptr.h" 14#include "base/message_loop/message_loop.h" 15#include "base/strings/string_number_conversions.h" 16#include "base/strings/string_split.h" 17#include "base/strings/string_util.h" 18#include "base/strings/stringprintf.h" 19#include "base/time/time.h" 20#include "net/base/address_list.h" 21#include "net/base/ip_endpoint.h" 22#include "net/base/net_errors.h" 23#include "net/base/net_log.h" 24#include "net/base/net_util.h" 25#include "net/dns/dns_client.h" 26#include "net/dns/dns_config_service.h" 27#include "net/dns/dns_protocol.h" 28#include "net/dns/host_cache.h" 29#include "net/dns/host_resolver_impl.h" 30#include "net/tools/gdig/file_net_log.h" 31 32#if defined(OS_MACOSX) 33#include "base/mac/scoped_nsautorelease_pool.h" 34#endif 35 36namespace net { 37 38namespace { 39 40bool StringToIPEndPoint(const std::string& ip_address_and_port, 41 IPEndPoint* ip_end_point) { 42 DCHECK(ip_end_point); 43 44 std::string ip; 45 int port; 46 if (!ParseHostAndPort(ip_address_and_port, &ip, &port)) 47 return false; 48 if (port == -1) 49 port = dns_protocol::kDefaultPort; 50 51 net::IPAddressNumber ip_number; 52 if (!net::ParseIPLiteralToNumber(ip, &ip_number)) 53 return false; 54 55 *ip_end_point = net::IPEndPoint(ip_number, port); 56 return true; 57} 58 59// Convert DnsConfig to human readable text omitting the hosts member. 60std::string DnsConfigToString(const DnsConfig& dns_config) { 61 std::string output; 62 output.append("search "); 63 for (size_t i = 0; i < dns_config.search.size(); ++i) { 64 output.append(dns_config.search[i] + " "); 65 } 66 output.append("\n"); 67 68 for (size_t i = 0; i < dns_config.nameservers.size(); ++i) { 69 output.append("nameserver "); 70 output.append(dns_config.nameservers[i].ToString()).append("\n"); 71 } 72 73 base::StringAppendF(&output, "options ndots:%d\n", dns_config.ndots); 74 base::StringAppendF(&output, "options timeout:%d\n", 75 static_cast<int>(dns_config.timeout.InMilliseconds())); 76 base::StringAppendF(&output, "options attempts:%d\n", dns_config.attempts); 77 if (dns_config.rotate) 78 output.append("options rotate\n"); 79 if (dns_config.edns0) 80 output.append("options edns0\n"); 81 return output; 82} 83 84// Convert DnsConfig hosts member to a human readable text. 85std::string DnsHostsToString(const DnsHosts& dns_hosts) { 86 std::string output; 87 for (DnsHosts::const_iterator i = dns_hosts.begin(); 88 i != dns_hosts.end(); 89 ++i) { 90 const DnsHostsKey& key = i->first; 91 std::string host_name = key.first; 92 output.append(IPEndPoint(i->second, -1).ToStringWithoutPort()); 93 output.append(" ").append(host_name).append("\n"); 94 } 95 return output; 96} 97 98struct ReplayLogEntry { 99 base::TimeDelta start_time; 100 std::string domain_name; 101}; 102 103typedef std::vector<ReplayLogEntry> ReplayLog; 104 105// Loads and parses a replay log file and fills |replay_log| with a structured 106// representation. Returns whether the operation was successful. If not, the 107// contents of |replay_log| are undefined. 108// 109// The replay log is a text file where each line contains 110// 111// timestamp_in_milliseconds domain_name 112// 113// The timestamp_in_milliseconds needs to be an integral delta from start of 114// resolution and is in milliseconds. domain_name is the name to be resolved. 115// 116// The file should be sorted by timestamp in ascending time. 117bool LoadReplayLog(const base::FilePath& file_path, ReplayLog* replay_log) { 118 std::string original_replay_log_contents; 119 if (!base::ReadFileToString(file_path, &original_replay_log_contents)) { 120 fprintf(stderr, "Unable to open replay file %s\n", 121 file_path.MaybeAsASCII().c_str()); 122 return false; 123 } 124 125 // Strip out \r characters for Windows files. This isn't as efficient as a 126 // smarter line splitter, but this particular use does not need to target 127 // efficiency. 128 std::string replay_log_contents; 129 base::RemoveChars(original_replay_log_contents, "\r", &replay_log_contents); 130 131 std::vector<std::string> lines; 132 base::SplitString(replay_log_contents, '\n', &lines); 133 base::TimeDelta previous_delta; 134 bool bad_parse = false; 135 for (unsigned i = 0; i < lines.size(); ++i) { 136 if (lines[i].empty()) 137 continue; 138 std::vector<std::string> time_and_name; 139 base::SplitString(lines[i], ' ', &time_and_name); 140 if (time_and_name.size() != 2) { 141 fprintf( 142 stderr, 143 "[%s %u] replay log should have format 'timestamp domain_name\\n'\n", 144 file_path.MaybeAsASCII().c_str(), 145 i + 1); 146 bad_parse = true; 147 continue; 148 } 149 150 int64 delta_in_milliseconds; 151 if (!base::StringToInt64(time_and_name[0], &delta_in_milliseconds)) { 152 fprintf( 153 stderr, 154 "[%s %u] replay log should have format 'timestamp domain_name\\n'\n", 155 file_path.MaybeAsASCII().c_str(), 156 i + 1); 157 bad_parse = true; 158 continue; 159 } 160 161 base::TimeDelta delta = 162 base::TimeDelta::FromMilliseconds(delta_in_milliseconds); 163 if (delta < previous_delta) { 164 fprintf( 165 stderr, 166 "[%s %u] replay log should be sorted by time\n", 167 file_path.MaybeAsASCII().c_str(), 168 i + 1); 169 bad_parse = true; 170 continue; 171 } 172 173 previous_delta = delta; 174 ReplayLogEntry entry; 175 entry.start_time = delta; 176 entry.domain_name = time_and_name[1]; 177 replay_log->push_back(entry); 178 } 179 return !bad_parse; 180} 181 182class GDig { 183 public: 184 GDig(); 185 ~GDig(); 186 187 enum Result { 188 RESULT_NO_RESOLVE = -3, 189 RESULT_NO_CONFIG = -2, 190 RESULT_WRONG_USAGE = -1, 191 RESULT_OK = 0, 192 RESULT_PENDING = 1, 193 }; 194 195 Result Main(int argc, const char* argv[]); 196 197 private: 198 bool ParseCommandLine(int argc, const char* argv[]); 199 200 void Start(); 201 void Finish(Result); 202 203 void OnDnsConfig(const DnsConfig& dns_config_const); 204 void OnResolveComplete(unsigned index, AddressList* address_list, 205 base::TimeDelta time_since_start, int val); 206 void OnTimeout(); 207 void ReplayNextEntry(); 208 209 base::TimeDelta config_timeout_; 210 bool print_config_; 211 bool print_hosts_; 212 net::IPEndPoint nameserver_; 213 base::TimeDelta timeout_; 214 int parallellism_; 215 ReplayLog replay_log_; 216 unsigned replay_log_index_; 217 base::Time start_time_; 218 int active_resolves_; 219 Result result_; 220 221 base::CancelableClosure timeout_closure_; 222 scoped_ptr<DnsConfigService> dns_config_service_; 223 scoped_ptr<FileNetLogObserver> log_observer_; 224 scoped_ptr<NetLog> log_; 225 scoped_ptr<HostResolver> resolver_; 226 227#if defined(OS_MACOSX) 228 // Without this there will be a mem leak on osx. 229 base::mac::ScopedNSAutoreleasePool scoped_pool_; 230#endif 231 232 // Need AtExitManager to support AsWeakPtr (in NetLog). 233 base::AtExitManager exit_manager_; 234}; 235 236GDig::GDig() 237 : config_timeout_(base::TimeDelta::FromSeconds(5)), 238 print_config_(false), 239 print_hosts_(false), 240 parallellism_(6), 241 replay_log_index_(0u), 242 active_resolves_(0) { 243} 244 245GDig::~GDig() { 246 if (log_) 247 log_->RemoveThreadSafeObserver(log_observer_.get()); 248} 249 250GDig::Result GDig::Main(int argc, const char* argv[]) { 251 if (!ParseCommandLine(argc, argv)) { 252 fprintf(stderr, 253 "usage: %s [--net_log[=<basic|no_bytes|all>]]" 254 " [--print_config] [--print_hosts]" 255 " [--nameserver=<ip_address[:port]>]" 256 " [--timeout=<milliseconds>]" 257 " [--config_timeout=<seconds>]" 258 " [--j=<parallel resolves>]" 259 " [--replay_file=<path>]" 260 " [domain_name]\n", 261 argv[0]); 262 return RESULT_WRONG_USAGE; 263 } 264 265 base::MessageLoopForIO loop; 266 267 result_ = RESULT_PENDING; 268 Start(); 269 if (result_ == RESULT_PENDING) 270 base::MessageLoop::current()->Run(); 271 272 // Destroy it while MessageLoopForIO is alive. 273 dns_config_service_.reset(); 274 return result_; 275} 276 277bool GDig::ParseCommandLine(int argc, const char* argv[]) { 278 CommandLine::Init(argc, argv); 279 const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); 280 281 if (parsed_command_line.HasSwitch("config_timeout")) { 282 int timeout_seconds = 0; 283 bool parsed = base::StringToInt( 284 parsed_command_line.GetSwitchValueASCII("config_timeout"), 285 &timeout_seconds); 286 if (parsed && timeout_seconds > 0) { 287 config_timeout_ = base::TimeDelta::FromSeconds(timeout_seconds); 288 } else { 289 fprintf(stderr, "Invalid config_timeout parameter\n"); 290 return false; 291 } 292 } 293 294 if (parsed_command_line.HasSwitch("net_log")) { 295 std::string log_param = parsed_command_line.GetSwitchValueASCII("net_log"); 296 NetLog::LogLevel level = NetLog::LOG_ALL_BUT_BYTES; 297 298 if (log_param.length() > 0) { 299 std::map<std::string, NetLog::LogLevel> log_levels; 300 log_levels["all"] = NetLog::LOG_ALL; 301 log_levels["no_bytes"] = NetLog::LOG_ALL_BUT_BYTES; 302 log_levels["basic"] = NetLog::LOG_BASIC; 303 304 if (log_levels.find(log_param) != log_levels.end()) { 305 level = log_levels[log_param]; 306 } else { 307 fprintf(stderr, "Invalid net_log parameter\n"); 308 return false; 309 } 310 } 311 log_.reset(new NetLog); 312 log_observer_.reset(new FileNetLogObserver(stderr)); 313 log_->AddThreadSafeObserver(log_observer_.get(), level); 314 } 315 316 print_config_ = parsed_command_line.HasSwitch("print_config"); 317 print_hosts_ = parsed_command_line.HasSwitch("print_hosts"); 318 319 if (parsed_command_line.HasSwitch("nameserver")) { 320 std::string nameserver = 321 parsed_command_line.GetSwitchValueASCII("nameserver"); 322 if (!StringToIPEndPoint(nameserver, &nameserver_)) { 323 fprintf(stderr, 324 "Cannot parse the namerserver string into an IPEndPoint\n"); 325 return false; 326 } 327 } 328 329 if (parsed_command_line.HasSwitch("timeout")) { 330 int timeout_millis = 0; 331 bool parsed = base::StringToInt( 332 parsed_command_line.GetSwitchValueASCII("timeout"), 333 &timeout_millis); 334 if (parsed && timeout_millis > 0) { 335 timeout_ = base::TimeDelta::FromMilliseconds(timeout_millis); 336 } else { 337 fprintf(stderr, "Invalid timeout parameter\n"); 338 return false; 339 } 340 } 341 342 if (parsed_command_line.HasSwitch("replay_file")) { 343 base::FilePath replay_path = 344 parsed_command_line.GetSwitchValuePath("replay_file"); 345 if (!LoadReplayLog(replay_path, &replay_log_)) 346 return false; 347 } 348 349 if (parsed_command_line.HasSwitch("j")) { 350 int parallellism = 0; 351 bool parsed = base::StringToInt( 352 parsed_command_line.GetSwitchValueASCII("j"), 353 ¶llellism); 354 if (parsed && parallellism > 0) { 355 parallellism_ = parallellism; 356 } else { 357 fprintf(stderr, "Invalid parallellism parameter\n"); 358 } 359 } 360 361 if (parsed_command_line.GetArgs().size() == 1) { 362 ReplayLogEntry entry; 363 entry.start_time = base::TimeDelta(); 364#if defined(OS_WIN) 365 entry.domain_name = WideToASCII(parsed_command_line.GetArgs()[0]); 366#else 367 entry.domain_name = parsed_command_line.GetArgs()[0]; 368#endif 369 replay_log_.push_back(entry); 370 } else if (parsed_command_line.GetArgs().size() != 0) { 371 return false; 372 } 373 return print_config_ || print_hosts_ || !replay_log_.empty(); 374} 375 376void GDig::Start() { 377 if (nameserver_.address().size() > 0) { 378 DnsConfig dns_config; 379 dns_config.attempts = 1; 380 dns_config.nameservers.push_back(nameserver_); 381 OnDnsConfig(dns_config); 382 } else { 383 dns_config_service_ = DnsConfigService::CreateSystemService(); 384 dns_config_service_->ReadConfig(base::Bind(&GDig::OnDnsConfig, 385 base::Unretained(this))); 386 timeout_closure_.Reset(base::Bind(&GDig::OnTimeout, 387 base::Unretained(this))); 388 base::MessageLoop::current()->PostDelayedTask( 389 FROM_HERE, timeout_closure_.callback(), config_timeout_); 390 } 391} 392 393void GDig::Finish(Result result) { 394 DCHECK_NE(RESULT_PENDING, result); 395 result_ = result; 396 if (base::MessageLoop::current()) 397 base::MessageLoop::current()->Quit(); 398} 399 400void GDig::OnDnsConfig(const DnsConfig& dns_config_const) { 401 timeout_closure_.Cancel(); 402 DCHECK(dns_config_const.IsValid()); 403 DnsConfig dns_config = dns_config_const; 404 405 if (timeout_.InMilliseconds() > 0) 406 dns_config.timeout = timeout_; 407 if (print_config_) { 408 printf("# Dns Configuration\n" 409 "%s", DnsConfigToString(dns_config).c_str()); 410 } 411 if (print_hosts_) { 412 printf("# Host Database\n" 413 "%s", DnsHostsToString(dns_config.hosts).c_str()); 414 } 415 416 if (replay_log_.empty()) { 417 Finish(RESULT_OK); 418 return; 419 } 420 421 scoped_ptr<DnsClient> dns_client(DnsClient::CreateClient(NULL)); 422 dns_client->SetConfig(dns_config); 423 scoped_ptr<HostResolverImpl> resolver( 424 new HostResolverImpl( 425 HostCache::CreateDefaultCache(), 426 PrioritizedDispatcher::Limits(NUM_PRIORITIES, parallellism_), 427 HostResolverImpl::ProcTaskParams(NULL, 1), 428 log_.get())); 429 resolver->SetDnsClient(dns_client.Pass()); 430 resolver_ = resolver.Pass(); 431 432 start_time_ = base::Time::Now(); 433 434 ReplayNextEntry(); 435} 436 437void GDig::ReplayNextEntry() { 438 DCHECK_LT(replay_log_index_, replay_log_.size()); 439 440 base::TimeDelta time_since_start = base::Time::Now() - start_time_; 441 while (replay_log_index_ < replay_log_.size()) { 442 const ReplayLogEntry& entry = replay_log_[replay_log_index_]; 443 if (time_since_start < entry.start_time) { 444 // Delay call to next time and return. 445 base::MessageLoop::current()->PostDelayedTask( 446 FROM_HERE, 447 base::Bind(&GDig::ReplayNextEntry, base::Unretained(this)), 448 entry.start_time - time_since_start); 449 return; 450 } 451 452 HostResolver::RequestInfo info(HostPortPair(entry.domain_name.c_str(), 80)); 453 AddressList* addrlist = new AddressList(); 454 unsigned current_index = replay_log_index_; 455 CompletionCallback callback = base::Bind(&GDig::OnResolveComplete, 456 base::Unretained(this), 457 current_index, 458 base::Owned(addrlist), 459 time_since_start); 460 ++active_resolves_; 461 ++replay_log_index_; 462 int ret = resolver_->Resolve( 463 info, 464 DEFAULT_PRIORITY, 465 addrlist, 466 callback, 467 NULL, 468 BoundNetLog::Make(log_.get(), net::NetLog::SOURCE_NONE)); 469 if (ret != ERR_IO_PENDING) 470 callback.Run(ret); 471 } 472} 473 474void GDig::OnResolveComplete(unsigned entry_index, 475 AddressList* address_list, 476 base::TimeDelta resolve_start_time, 477 int val) { 478 DCHECK_GT(active_resolves_, 0); 479 DCHECK(address_list); 480 DCHECK_LT(entry_index, replay_log_.size()); 481 --active_resolves_; 482 base::TimeDelta resolve_end_time = base::Time::Now() - start_time_; 483 base::TimeDelta resolve_time = resolve_end_time - resolve_start_time; 484 printf("%u %d %d %s %d ", 485 entry_index, 486 static_cast<int>(resolve_end_time.InMilliseconds()), 487 static_cast<int>(resolve_time.InMilliseconds()), 488 replay_log_[entry_index].domain_name.c_str(), val); 489 if (val != OK) { 490 printf("%s", ErrorToString(val)); 491 } else { 492 for (size_t i = 0; i < address_list->size(); ++i) { 493 if (i != 0) 494 printf(" "); 495 printf("%s", (*address_list)[i].ToStringWithoutPort().c_str()); 496 } 497 } 498 printf("\n"); 499 if (active_resolves_ == 0 && replay_log_index_ >= replay_log_.size()) 500 Finish(RESULT_OK); 501} 502 503void GDig::OnTimeout() { 504 fprintf(stderr, "Timed out waiting to load the dns config\n"); 505 Finish(RESULT_NO_CONFIG); 506} 507 508} // empty namespace 509 510} // namespace net 511 512int main(int argc, const char* argv[]) { 513 net::GDig dig; 514 return dig.Main(argc, argv); 515} 516