Bindings for libuv-integrated c-ares
authorBert Belder <bertbelder@gmail.com>
Mon, 4 Jul 2011 22:17:20 +0000 (00:17 +0200)
committerBert Belder <bertbelder@gmail.com>
Mon, 4 Jul 2011 22:17:20 +0000 (00:17 +0200)
lib/dns_legacy.js [moved from lib/dns.js with 100% similarity]
lib/dns_uv.js [new file with mode: 0644]
src/cares_wrap.cc [new file with mode: 0644]
src/node.js
src/node_extensions.h
test/internet/test-dns.js [new file with mode: 0644]
test/simple/test-c-ares.js
wscript

similarity index 100%
rename from lib/dns.js
rename to lib/dns_legacy.js
diff --git a/lib/dns_uv.js b/lib/dns_uv.js
new file mode 100644 (file)
index 0000000..7a313a7
--- /dev/null
@@ -0,0 +1,173 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// 'Software'), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var cares = process.binding('cares_wrap'),
+    net = require('net'),
+    isIp = net.isIP;
+
+
+function errnoException(errorno, syscall) {
+  // TODO make this more compatible with ErrnoException from src/node.cc
+  // Once all of Node is using this function the ErrnoException from
+  // src/node.cc should be removed.
+  var e = new Error(syscall + ' ' + errorno);
+  e.errno = errorno;
+  e.syscall = syscall;
+  return e;
+}
+
+
+function familyToSym(family) {
+  switch (family) {
+    case 4: return cares.AF_INET;
+    case 6: return cares.AF_INET6;
+    default: return cares.AF_UNSPEC;
+  }
+}
+
+
+function symToFamily(family) {
+  switch (family) {
+    case cares.AF_INET: return 4;
+    case cares.AF_INET6: return 6;
+    default: return undefined;
+  }
+}
+
+
+// Easy DNS A/AAAA look up
+// lookup(domain, [family,] callback)
+exports.lookup = function(domain, family, callback) {
+  // parse arguments
+  if (arguments.length === 2) {
+    callback = family;
+    family = 0;
+  } else if (!family) {
+    family = 0;
+  } else {
+    family = +family;
+    if (family !== 4 && family !== 6) {
+      throw new Error('invalid argument: `family` must be 4 or 6');
+    }
+  }
+
+  if (!domain) {
+    callback(null, null, family === 6 ? 6 : 4);
+    return {};
+  }
+
+  var matchedFamily = net.isIP(domain);
+  if (matchedFamily) {
+    callback(null, domain, matchedFamily);
+    return {};
+  }
+
+  /* TODO
+  if (/\w\.local\.?$/.test(domain)) {
+    // ANNOYING: In the case of mDNS domains use NSS in the thread pool.
+    // I wish c-ares had better support.
+    process.binding('net').getaddrinfo(domain, 4, function(err, domains4) {
+      callback(err, domains4[0], 4);
+    });
+    return {};
+  } */
+
+  function onanswer(status, addresses, familySym) {
+    if (!status) {
+      callback(null, addresses[0], symToFamily(familySym));
+    } else {
+      callback(errnoException(errno, 'getHostByName'));
+    }
+  }
+
+  var wrap = cares.getHostByName(domain, familyToSym(family), onanswer);
+  if (!wrap) {
+    throw errnoException(errno, 'getHostByName');
+  }
+
+  return wrap;
+};
+
+
+function resolver(bindingName) {
+  var binding = cares[bindingName];
+
+  return function query(name, callback) {
+    function onanswer(status, result) {
+      if (!status) {
+        callback(null, result);
+      } else {
+        callback(errnoException(errno, bindingName));
+      }
+    }
+
+    var wrap = binding(name, onanswer);
+    if (!wrap) {
+      throw errnoException(errno, bindingName);
+    }
+
+    return wrap;
+  }
+}
+
+
+var resolveMap = {};
+exports.resolve4     = resolveMap.A     = resolver('queryA');
+exports.resolve6     = resolveMap.AAAA  = resolver('queryAaaa');
+exports.resolveCname = resolveMap.CNAME = resolver('queryCname');
+exports.resolveMx    = resolveMap.MX    = resolver('queryMx');
+exports.resolveNs    = resolveMap.NS    = resolver('queryNs');
+exports.resolveTxt   = resolveMap.TXT   = resolver('queryTxt');
+exports.resolveSrv   = resolveMap.SRV   = resolver('querySrv');
+exports.reverse      = resolveMap.PTR   = resolver('getHostByAddr');
+
+
+exports.resolve = function(domain, type_, callback_) {
+  var resolver, callback;
+  if (typeof type_ == 'string') {
+    resolver = resolveMap[type_];
+    callback = callback_;
+  } else {
+    resolver = exports.resolve4;
+    callback = type_;
+  }
+
+  if (typeof resolver === 'function') {
+    return resolver(domain, callback);
+  } else {
+    throw new Error('Unknown type "' + type + '"');
+  }
+};
+
+
+// ERROR CODES
+exports.BADNAME = 'EBADNAME';
+exports.BADRESP = 'EBADRESP';
+exports.CONNREFUSED = 'ECONNREFUSED';
+exports.DESTRUCTION = 'EDESTRUCTION';
+exports.REFUSED = 'EREFUSED';
+exports.FORMERR = 'EFORMERR';
+exports.NODATA = 'ENODATA';
+exports.NOMEM = 'ENOMEM';
+exports.NOTFOUND = 'ENOTFOUND';
+exports.NOTIMP = 'ENOTIMP';
+exports.SERVFAIL = 'ESERVFAIL';
+exports.TIMEOUT = 'ETIMEOUT';
diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc
new file mode 100644 (file)
index 0000000..3f610af
--- /dev/null
@@ -0,0 +1,601 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include <assert.h>
+#include <node.h>
+#include <uv.h>
+
+#if defined(__OpenBSD__) || defined(__MINGW32__)
+# include <nameser.h>
+#else
+# include <arpa/nameser.h>
+#endif
+
+// Temporary hack: libuv should provide uv_inet_pton and uv_inet_ntop.
+#ifdef __MINGW32__
+  extern "C" {
+#   include <inet_net_pton.h>
+#   include <inet_ntop.h>
+  }
+# define uv_inet_pton ares_inet_pton
+# define uv_inet_ntop ares_inet_ntop
+
+#else // __POSIX__
+# include <arpa/inet.h>
+# define uv_inet_pton inet_pton
+# define uv_inet_ntop inet_ntop
+#endif
+
+
+namespace node {
+
+namespace cares_wrap {
+
+using v8::Arguments;
+using v8::Array;
+using v8::Context;
+using v8::Function;
+using v8::Handle;
+using v8::HandleScope;
+using v8::Integer;
+using v8::Local;
+using v8::Null;
+using v8::Object;
+using v8::Persistent;
+using v8::String;
+using v8::Value;
+
+static Persistent<String> onanswer_sym;
+
+static ares_channel ares_channel;
+
+
+static Local<Array> HostentToAddresses(struct hostent* host) {
+  HandleScope scope;
+  Local<Array> addresses = Array::New();
+
+  char ip[INET6_ADDRSTRLEN];
+  for (int i = 0; host->h_addr_list[i]; ++i) {
+    uv_inet_ntop(host->h_addrtype, host->h_addr_list[i], ip, sizeof(ip));
+
+    Local<String> address = String::New(ip);
+    addresses->Set(Integer::New(i), address);
+  }
+
+  return scope.Close(addresses);
+}
+
+
+static Local<Array> HostentToNames(struct hostent* host) {
+  HandleScope scope;
+  Local<Array> names = Array::New();
+
+  for (int i = 0; host->h_aliases[i]; ++i) {
+    Local<String> address = String::New(host->h_aliases[i]);
+    names->Set(Integer::New(i), address);
+  }
+
+  return scope.Close(names);
+}
+
+
+static const char* AresErrnoString(int errorno) {
+  switch (errorno) {
+#define ERRNO_CASE(e) case ARES_##e: return #e;
+    ERRNO_CASE(SUCCESS)
+    ERRNO_CASE(ENODATA)
+    ERRNO_CASE(EFORMERR)
+    ERRNO_CASE(ESERVFAIL)
+    ERRNO_CASE(ENOTFOUND)
+    ERRNO_CASE(ENOTIMP)
+    ERRNO_CASE(EREFUSED)
+    ERRNO_CASE(EBADQUERY)
+    ERRNO_CASE(EBADNAME)
+    ERRNO_CASE(EBADFAMILY)
+    ERRNO_CASE(EBADRESP)
+    ERRNO_CASE(ECONNREFUSED)
+    ERRNO_CASE(ETIMEOUT)
+    ERRNO_CASE(EOF)
+    ERRNO_CASE(EFILE)
+    ERRNO_CASE(ENOMEM)
+    ERRNO_CASE(EDESTRUCTION)
+    ERRNO_CASE(EBADSTR)
+    ERRNO_CASE(EBADFLAGS)
+    ERRNO_CASE(ENONAME)
+    ERRNO_CASE(EBADHINTS)
+    ERRNO_CASE(ENOTINITIALIZED)
+    ERRNO_CASE(ELOADIPHLPAPI)
+    ERRNO_CASE(EADDRGETNETWORKPARAMS)
+    ERRNO_CASE(ECANCELLED)
+#undef ERRNO_CASE
+    default:
+      assert(0 && "Unhandled c-ares error");
+      return "(UNKNOWN)";
+  }
+}
+
+
+static void SetAresErrno(int errorno) {
+  HandleScope scope;
+  Handle<Value> key = String::NewSymbol("errno");
+  Handle<Value> value = String::NewSymbol(AresErrnoString(errorno));
+  Context::GetCurrent()->Global()->Set(key, value);
+}
+
+
+class QueryWrap {
+ public:
+  QueryWrap() {
+    HandleScope scope;
+
+    object_ = Persistent<Object>::New(Object::New());
+  }
+
+  ~QueryWrap() {
+    assert(!object_.IsEmpty());
+
+    object_->DeleteHiddenValue(onanswer_sym);
+
+    object_.Dispose();
+    object_.Clear();
+  }
+
+  Handle<Object> GetObject() {
+    return object_;
+  }
+
+  void SetOnAnswer(Handle<Value> onanswer) {
+    assert(onanswer->IsFunction());
+    object_->SetHiddenValue(onanswer_sym, onanswer);
+  }
+
+  // Subclasses should implement the appropriate Send method.
+  virtual int Send(const char* name) {
+    assert(0);
+  }
+
+  virtual int Send(const char* name, int family) {
+    assert(0);
+  }
+
+ protected:
+  void* GetQueryArg() {
+    return static_cast<void*>(this);
+  }
+
+  static void Callback(void *arg, int status, int timeouts,
+      unsigned char* answer_buf, int answer_len) {
+    QueryWrap* wrap = reinterpret_cast<QueryWrap*>(arg);
+
+    if (status != ARES_SUCCESS) {
+      wrap->ParseError(status);
+    } else {
+      wrap->Parse(answer_buf, answer_len);
+    }
+
+    delete wrap;
+  }
+
+  static void Callback(void *arg, int status, int timeouts,
+      struct hostent* host) {
+    QueryWrap* wrap = reinterpret_cast<QueryWrap*>(arg);
+
+    if (status != ARES_SUCCESS) {
+      wrap->ParseError(status);
+    } else {
+      wrap->Parse(host);
+    }
+
+    delete wrap;
+  }
+
+  Handle<Function> GetOnAnswer() {
+    HandleScope scope;
+    assert(!object_.IsEmpty());
+    Handle<Value> onanswer = object_->GetHiddenValue(onanswer_sym);
+    assert(onanswer->IsFunction());
+    return scope.Close(Handle<Function>::Cast(onanswer));
+  }
+
+  void CallOnAnswer(Local<Value> answer) {
+    HandleScope scope;
+    Local<Value> argv[2] = { Integer::New(0), answer };
+    GetOnAnswer()->Call(this->object_, 2, argv);
+  }
+
+  void CallOnAnswer(Local<Value> answer, Local<Value> family) {
+    HandleScope scope;
+    Local<Value> argv[3] = { Integer::New(0), answer, family };
+    GetOnAnswer()->Call(this->object_, 3, argv);
+  }
+
+  void ParseError(int status) {
+    assert(status != ARES_SUCCESS);
+    SetAresErrno(status);
+
+    HandleScope scope;
+    Local<Value> argv[1] = { Integer::New(-1) };
+    GetOnAnswer()->Call(this->object_, 1, argv);
+  }
+
+  // Subclasses should implement the appropriate Parse method.
+  virtual void Parse(unsigned char* buf, int len) {
+    assert(0);
+  };
+
+  virtual void Parse(struct hostent* host) {
+    assert(0);
+  };
+
+ private:
+  Persistent<Object> object_;
+};
+
+
+class QueryAWrap: public QueryWrap {
+ public:
+  int Send(const char* name) {
+    ares_query(ares_channel, name, ns_c_in, ns_t_a, Callback, GetQueryArg());
+    return 0;
+  }
+
+ protected:
+  void Parse(unsigned char* buf, int len) {
+    HandleScope scope;
+
+    struct hostent* host;
+
+    int status = ares_parse_a_reply(buf, len, &host, NULL, NULL);
+    if (status != ARES_SUCCESS) {
+      this->ParseError(status);
+      return;
+    }
+
+    Local<Array> addresses = HostentToAddresses(host);
+    ares_free_hostent(host);
+
+    this->CallOnAnswer(addresses);
+  }
+};
+
+
+class QueryAaaaWrap: public QueryWrap {
+ public:
+  int Send(const char* name) {
+    ares_query(ares_channel,
+               name,
+               ns_c_in,
+               ns_t_aaaa,
+               Callback,
+               GetQueryArg());
+    return 0;
+  }
+
+ protected:
+  void Parse(unsigned char* buf, int len) {
+    HandleScope scope;
+
+    struct hostent* host;
+
+    int status = ares_parse_aaaa_reply(buf, len, &host, NULL, NULL);
+    if (status != ARES_SUCCESS) {
+      this->ParseError(status);
+      return;
+    }
+
+    Local<Array> addresses = HostentToAddresses(host);
+    ares_free_hostent(host);
+
+    this->CallOnAnswer(addresses);
+  }
+};
+
+
+class QueryCnameWrap: public QueryWrap {
+ public:
+  int Send(const char* name) {
+    ares_query(ares_channel,
+               name,
+               ns_c_in,
+               ns_t_cname,
+               Callback,
+               GetQueryArg());
+    return 0;
+  }
+
+ protected:
+  void Parse(unsigned char* buf, int len) {
+    HandleScope scope;
+
+    struct hostent* host;
+
+    int status = ares_parse_a_reply(buf, len, &host, NULL, NULL);
+    if (status != ARES_SUCCESS) {
+      this->ParseError(status);
+      return;
+    }
+
+    // A cname lookup always returns a single record but we follow the
+    // common API here.
+    Local<Array> result = Array::New(1);
+    result->Set(0, String::New(host->h_name));
+    ares_free_hostent(host);
+
+    this->CallOnAnswer(result);
+  }
+};
+
+
+class QueryMxWrap: public QueryWrap {
+ public:
+  int Send(const char* name) {
+    ares_query(ares_channel, name, ns_c_in, ns_t_mx, Callback, GetQueryArg());
+    return 0;
+  }
+
+ protected:
+  void Parse(unsigned char* buf, int len) {
+    HandleScope scope;
+
+    struct ares_mx_reply* mx_start;
+    int status = ares_parse_mx_reply(buf, len, &mx_start);
+    if (status != ARES_SUCCESS) {
+      this->ParseError(status);
+      return;
+    }
+
+    Local<Array> mx_records = Array::New();
+    Local<String> exchange_symbol = String::NewSymbol("exchange");
+    Local<String> priority_symbol = String::NewSymbol("priority");
+    int i = 0;
+    for (struct ares_mx_reply* mx_current = mx_start;
+         mx_current;
+         mx_current = mx_current->next) {
+      Local<Object> mx_record = Object::New();
+      mx_record->Set(exchange_symbol, String::New(mx_current->host));
+      mx_record->Set(priority_symbol, Integer::New(mx_current->priority));
+      mx_records->Set(Integer::New(i++), mx_record);
+    }
+
+    ares_free_data(mx_start);
+
+    this->CallOnAnswer(mx_records);
+  }
+};
+
+
+class QueryNsWrap: public QueryWrap {
+ public:
+  int Send(const char* name) {
+    ares_query(ares_channel, name, ns_c_in, ns_t_ns, Callback, GetQueryArg());
+    return 0;
+  }
+
+ protected:
+  void Parse(unsigned char* buf, int len) {
+    struct hostent* host;
+
+    int status = ares_parse_ns_reply(buf, len, &host);
+    if (status != ARES_SUCCESS) {
+      this->ParseError(status);
+      return;
+    }
+
+    Local<Array> names = HostentToNames(host);
+    ares_free_hostent(host);
+
+    this->CallOnAnswer(names);
+  }
+};
+
+
+class QuerySrvWrap: public QueryWrap {
+ public:
+  int Send(const char* name) {
+    ares_query(ares_channel,
+               name,
+               ns_c_in,
+               ns_t_srv,
+               Callback,
+               GetQueryArg());
+    return 0;
+  }
+
+ protected:
+  void Parse(unsigned char* buf, int len) {
+    HandleScope scope;
+
+    struct ares_srv_reply* srv_start;
+    int status = ares_parse_srv_reply(buf, len, &srv_start);
+    if (status != ARES_SUCCESS) {
+      this->ParseError(status);
+      return;
+    }
+
+    Local<Array> srv_records = Array::New();
+    Local<String> host_symbol = String::NewSymbol("host");
+    Local<String> port_symbol = String::NewSymbol("port");
+    Local<String> priority_symbol = String::NewSymbol("priority");
+    Local<String> weight_symbol = String::NewSymbol("weight");
+    int i = 0;
+    for (struct ares_srv_reply* srv_current = srv_start;
+         srv_current;
+         srv_current = srv_current->next) {
+      Local<Object> srv_record = Object::New();
+      srv_record->Set(host_symbol, String::New(srv_current->host));
+      srv_record->Set(port_symbol, Integer::New(srv_current->port));
+      srv_record->Set(priority_symbol, Integer::New(srv_current->priority));
+      srv_record->Set(weight_symbol, Integer::New(srv_current->weight));
+      srv_records->Set(Integer::New(i++), srv_record);
+    }
+
+    ares_free_data(srv_start);
+
+    this->CallOnAnswer(srv_records);
+  }
+};
+
+
+class GetHostByAddrWrap: public QueryWrap {
+ public:
+  int Send(const char* name) {
+    int length, family;
+    char address_buffer[sizeof(struct in6_addr)];
+
+    if (uv_inet_pton(AF_INET, name, &address_buffer) == 1) {
+      length = sizeof(struct in_addr);
+      family = AF_INET;
+    } else if (uv_inet_pton(AF_INET6, name, &address_buffer) == 1) {
+      length = sizeof(struct in6_addr);
+      family = AF_INET6;
+    } else {
+      return ARES_ENOTIMP;
+    }
+
+    ares_gethostbyaddr(ares_channel,
+                       address_buffer,
+                       length,
+                       family,
+                       Callback,
+                       GetQueryArg());
+    return 0;
+  }
+
+ protected:
+  void Parse(struct hostent* host) {
+    HandleScope scope;
+
+    this->CallOnAnswer(HostentToNames(host));
+  }
+};
+
+
+class GetHostByNameWrap: public QueryWrap {
+ public:
+  int Send(const char* name, int family) {
+    ares_gethostbyname(ares_channel, name, family, Callback, GetQueryArg());
+    return 0;
+  }
+
+ protected:
+  void Parse(struct hostent* host) {
+    HandleScope scope;
+
+    Local<Array> addresses = HostentToAddresses(host);
+    Local<Integer> family = Integer::New(host->h_addrtype);
+
+    this->CallOnAnswer(addresses, family);
+  }
+};
+
+
+template <class Wrap>
+static Handle<Value> Query(const Arguments& args) {
+  HandleScope scope;
+
+  assert(!args.IsConstructCall());
+  assert(args.Length() >= 2);
+  assert(args[1]->IsFunction());
+
+  Wrap* wrap = new Wrap();
+  wrap->SetOnAnswer(args[1]);
+
+  // We must cache the wrap's js object here, because cares might make the
+  // callback from the wrap->Send stack. This will destroy the wrap's internal
+  // object reference, causing wrap->GetObject() to return undefined.
+  Local<Object> object = Local<Object>::New(wrap->GetObject());
+
+  String::Utf8Value name(args[0]->ToString());
+
+  int r = wrap->Send(*name);
+  if (r) {
+    SetAresErrno(r);
+    delete wrap;
+    return scope.Close(v8::Null());
+  } else {
+    return scope.Close(object);
+  }
+}
+
+
+template <class Wrap>
+static Handle<Value> QueryWithFamily(const Arguments& args) {
+  HandleScope scope;
+
+  assert(!args.IsConstructCall());
+  assert(args.Length() >= 3);
+  assert(args[2]->IsFunction());
+
+  Wrap* wrap = new Wrap();
+  wrap->SetOnAnswer(args[2]);
+
+  // We must cache the wrap's js object here, because cares might make the
+  // callback from the wrap->Send stack. This will destroy the wrap's internal
+  // object reference, causing wrap->GetObject() to return undefined.
+  Local<Object> object = Local<Object>::New(wrap->GetObject());
+
+  String::Utf8Value name(args[0]->ToString());
+  int family = args[1]->Int32Value();
+
+  int r = wrap->Send(*name, family);
+  if (r) {
+    SetAresErrno(r);
+    delete wrap;
+    return scope.Close(v8::Null());
+  } else {
+    return scope.Close(object);
+  }
+}
+
+
+static void Initialize(Handle<Object> target) {
+  HandleScope scope;
+  int r;
+
+  r = ares_library_init(ARES_LIB_INIT_ALL);
+  assert(r == ARES_SUCCESS);
+
+  struct ares_options options;
+  uv_ares_init_options(&ares_channel, &options, 0);
+  assert(r == 0);
+
+  NODE_SET_METHOD(target, "queryA", Query<QueryAWrap>);
+  NODE_SET_METHOD(target, "queryAaaa", Query<QueryAaaaWrap>);
+  NODE_SET_METHOD(target, "queryCname", Query<QueryCnameWrap>);
+  NODE_SET_METHOD(target, "queryMx", Query<QueryMxWrap>);
+  NODE_SET_METHOD(target, "queryNs", Query<QueryNsWrap>);
+  NODE_SET_METHOD(target, "querySrv", Query<QuerySrvWrap>);
+  NODE_SET_METHOD(target, "getHostByAddr", Query<GetHostByAddrWrap>);
+  NODE_SET_METHOD(target, "getHostByName", QueryWithFamily<GetHostByNameWrap>);
+
+  target->Set(String::NewSymbol("AF_INET"), Integer::New(AF_INET));
+  target->Set(String::NewSymbol("AF_INET6"), Integer::New(AF_INET6));
+  target->Set(String::NewSymbol("AF_UNSPEC"), Integer::New(AF_UNSPEC));
+
+  onanswer_sym = Persistent<String>::New(String::NewSymbol("onanswer"));
+}
+
+
+} // namespace cares_wrap
+
+}  // namespace node
+
+NODE_MODULE(node_cares_wrap, node::cares_wrap::Initialize);
index 50205c7..8be8594 100644 (file)
       case 'timers':
         return process.useUV ? 'timers_uv' : 'timers_legacy';
 
+      case 'dns':
+        return process.useUV ? 'dns_uv' : 'dns_legacy';
+
       default:
         return id;
     }
index a001584..57d4f8d 100644 (file)
@@ -44,6 +44,7 @@ NODE_EXT_LIST_ITEM(node_os)
 // libuv rewrite
 NODE_EXT_LIST_ITEM(node_timer_wrap)
 NODE_EXT_LIST_ITEM(node_tcp_wrap)
+NODE_EXT_LIST_ITEM(node_cares_wrap)
 
 NODE_EXT_LIST_END
 
diff --git a/test/internet/test-dns.js b/test/internet/test-dns.js
new file mode 100644 (file)
index 0000000..b833a35
--- /dev/null
@@ -0,0 +1,370 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var assert = require('assert');
+    dns = require('dns'),
+    net = require('net_uv');
+    isIP = net.isIP,
+    isIPv4 = net.isIPv4,
+    isIPv6 = net.isIPv6;
+
+var expected = 0,
+    completed = 0,
+    running = false,
+    queue = [];
+
+
+function TEST(f) {
+  function next() {
+    var f = queue.shift();
+    if (f) {
+      running = true;
+      console.log(f.name);
+      f(done);
+    }
+  }
+
+  function done() {
+    running = false;
+    completed++;
+    process.nextTick(next);
+  }
+
+  expected++;
+  queue.push(f);
+
+  if (!running) {
+    next();
+  }
+}
+
+
+process.on('exit', function() {
+  console.log(completed + " tests completed");
+  assert.equal(running, false);
+  assert.strictEqual(expected, completed);
+});
+
+
+TEST(function test_resolve4(done) {
+  var req = dns.resolve4('www.google.com', function(err, ips) {
+    if (err) throw err;
+
+    assert.ok(ips.length > 0);
+
+    for (var i = 0; i < ips.length; i++) {
+      assert.ok(isIPv4(ips[i]));
+    }
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+TEST(function test_resolve6(done) {
+  var req = dns.resolve6('ipv6.google.com', function(err, ips) {
+    if (err) throw err;
+
+    assert.ok(ips.length > 0);
+
+    for (var i = 0; i < ips.length; i++) {
+      assert.ok(isIPv6(ips[i]));
+    }
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+TEST(function test_reverse_ipv4(done) {
+  var req = dns.reverse('8.8.8.8', function(err, domains) {
+    if (err) throw err;
+
+    assert.ok(domains.length > 0);
+
+    for (var i = 0; i < domains.length; i++) {
+      assert.ok(domains[i]);
+      assert.ok(typeof domains[i] === 'string');
+    }
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+TEST(function test_reverse_ipv6(done) {
+  var req = dns.reverse('2001:4860:4860::8888', function(err, domains) {
+    if (err) throw err;
+
+    assert.ok(domains.length > 0);
+
+    for (var i = 0; i < domains.length; i++) {
+      assert.ok(domains[i]);
+      assert.ok(typeof domains[i] === 'string');
+    }
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+TEST(function test_reverse_bogus(done) {
+  var error;
+
+  try {
+    var req = dns.reverse('bogus ip', function() {
+      assert.ok(false);
+    });
+  } catch(e) {
+    error = e;
+  }
+
+  assert.ok(error instanceof Error);
+  assert.strictEqual(error.errno, "ENOTIMP");
+
+  done();
+});
+
+
+TEST(function test_resolveMx(done) {
+  var req = dns.resolveMx('gmail.com', function(err, result) {
+    if (err) throw err;
+
+    assert.ok(result.length > 0);
+
+    for (var i = 0; i < result.length; i++) {
+      var item = result[i];
+      assert.ok(item);
+      assert.ok(typeof item === 'object');
+
+      assert.ok(item.exchange);
+      assert.ok(typeof item.exchange === 'string');
+
+      assert.ok(typeof item.priority === 'number');
+    }
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+TEST(function test_resolveNs(done) {
+  var req = dns.resolveNs('rackspace.com', function(err, names) {
+    if (err) throw err;
+
+    assert.ok(names.length > 0);
+
+    for (var i = 0; i < names.length; i++) {
+      var name = names[i];
+      assert.ok(name);
+      assert.ok(typeof name === 'string');
+    }
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+TEST(function test_resolveSrv(done) {
+  var req = dns.resolveSrv('_jabber._tcp.google.com', function(err, result) {
+    if (err) throw err;
+
+    assert.ok(result.length > 0);
+
+    for (var i = 0; i < result.length; i++) {
+      var item = result[i];
+      assert.ok(item);
+      assert.ok(typeof item === 'object');
+
+      assert.ok(item.host);
+      assert.ok(typeof item.host === 'string');
+
+      assert.ok(typeof item.port === 'number');
+      assert.ok(typeof item.priority === 'number');
+      assert.ok(typeof item.weight === 'number');
+    }
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+TEST(function test_resolveCname(done) {
+  var req = dns.resolveCname('www.google.com', function(err, names) {
+    if (err) throw err;
+
+    assert.ok(names.length > 0);
+
+    for (var i = 0; i < names.length; i++) {
+      var name = names[i];
+      assert.ok(name);
+      assert.ok(typeof name === 'string');
+    }
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+TEST(function test_lookup_ipv4_explicit(done) {
+  var req = dns.lookup('www.google.com', 4, function(err, ip, family) {
+    if (err) throw err;
+    assert.ok(net.isIPv4(ip));
+    assert.strictEqual(family, 4);
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+TEST(function test_lookup_ipv4_implicit(done) {
+  var req = dns.lookup('www.google.com', function(err, ip, family) {
+    if (err) throw err;
+    assert.ok(net.isIPv4(ip));
+    assert.strictEqual(family, 4);
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+TEST(function test_lookup_ipv6_explicit(done) {
+  var req = dns.lookup('ipv6.google.com', 6, function(err, ip, family) {
+    if (err) throw err;
+    assert.ok(net.isIPv6(ip));
+    assert.strictEqual(family, 6);
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+TEST(function test_lookup_ipv6_implicit(done) {
+  var req = dns.lookup('ipv6.google.com', function(err, ip, family) {
+    if (err) throw err;
+    assert.ok(net.isIPv6(ip));
+    assert.strictEqual(family, 6);
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+TEST(function test_lookup_failure(done) {
+  var req = dns.lookup('does.not.exist', 4, function(err, ip, family) {
+    assert.ok(err instanceof Error);
+    assert.strictEqual(err.errno, 'ENOTFOUND');
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+TEST(function test_lookup_null(done) {
+  var req = dns.lookup(null, function(err, ip, family) {
+    if (err) throw err;
+    assert.strictEqual(ip, null);
+    assert.strictEqual(family, 4);
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+TEST(function test_lookup_ip_ipv4(done) {
+  var req = dns.lookup("127.0.0.1", function(err, ip, family) {
+    if (err) throw err;
+    assert.strictEqual(ip, "127.0.0.1");
+    assert.strictEqual(family, 4);
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+TEST(function test_lookup_ip_ipv6(done) {
+  var req = dns.lookup("::1", function(err, ip, family) {
+    if (err) throw err;
+    assert.ok(net.isIPv6(ip));
+    assert.strictEqual(family, 6);
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+TEST(function test_lookup_localhost_ipv4(done) {
+  var req = dns.lookup("localhost", 4, function(err, ip, family) {
+    if (err) throw err;
+    assert.strictEqual(ip, "127.0.0.1");
+    assert.strictEqual(family, 4);
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+});
+
+
+/* Disabled because it appears to be not working on linux. */
+/* TEST(function test_lookup_localhost_ipv6(done) {
+  var req = dns.lookup("localhost", 6, function(err, ip, family) {
+    if (err) throw err;
+    assert.ok(net.isIPv6(ip));
+    assert.strictEqual(family, 6);
+
+    done();
+  });
+
+  assert.ok(typeof req === 'object');
+}); */
index 2bf3a11..03e5413 100644 (file)
@@ -27,16 +27,6 @@ var dns = require('dns');
 
 // Try resolution without callback
 
-dns.getHostByName('localhost', function(error, result) {
-  console.dir(result);
-  assert.deepEqual(['127.0.0.1'], result);
-});
-
-dns.getHostByName('127.0.0.1', function(error, result) {
-  console.dir(result);
-  assert.deepEqual(['127.0.0.1'], result);
-});
-
 dns.lookup(null, function(error, result, addressType) {
   assert.equal(null, result);
   assert.equal(4, addressType);
diff --git a/wscript b/wscript
index e85ac15..1e35c0f 100644 (file)
--- a/wscript
+++ b/wscript
@@ -819,6 +819,7 @@ def build(bld):
     src/node_string.cc
     src/timer_wrap.cc
     src/tcp_wrap.cc
+    src/cares_wrap.cc
   """
 
   if sys.platform.startswith("win32"):