summaryrefslogtreecommitdiffstats
path: root/net/tools/hresolv/hresolv.cc
blob: b5af4ae439e6d18641c78979dac164662c0def1c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
// Copyright (c) 2010 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/message_loop.h"
#include "base/string_number_conversions.h"
#include "base/string_split.h"
#include "base/string_util.h"
#include "base/thread.h"
#include "base/time.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_MACOSX)
  {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 = base::StringPrintf("%s  ai_canonname: %s\n",
                                      indent,
                                      ai.ai_canonname);
  }
  return base::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(net::HostPortPair(host_, 80));
    int rv = resolver_->Resolve(request_info,
                                &address_list_,
                                callback,
                                NULL,
                                net::BoundNetLog());
    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_;
  net::HostResolver* const 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_;
  net::HostResolver* const 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 = base::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 = base::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<CommandLine::StringType>& args,
    std::vector<HostAndTime>* hosts_and_times) {
  for (std::vector<CommandLine::StringType>::const_iterator it =
           args.begin();
       it != args.end();
       ++it) {
    // TODO(cbentzel): Read time offset.
#if defined(OS_WIN)
    HostAndTime host_and_time = {WideToASCII(*it), 0};
#else
    HostAndTime host_and_time = {*it, 0};
#endif
    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.
  base::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 (!base::StringToInt(tokens[1], &timestamp)) {
          // 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->args(),
                                          &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));

  net::HostResolverImpl host_resolver(NULL, cache, 100u, NULL);
  ResolverInvoker invoker(&host_resolver);
  invoker.ResolveAll(hosts_and_times, options.async);

  CommandLine::Reset();
  return 0;
}