From 858f23094e9f9382c780e5f11f13273242c9b26a Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Tue, 5 Jul 2011 00:17:20 +0200 Subject: [PATCH] Bindings for libuv-integrated c-ares --- lib/{dns.js => dns_legacy.js} | 0 lib/dns_uv.js | 173 ++++++++++ src/cares_wrap.cc | 601 ++++++++++++++++++++++++++++++++++ src/node.js | 3 + src/node_extensions.h | 1 + test/internet/test-dns.js | 370 +++++++++++++++++++++ test/simple/test-c-ares.js | 10 - wscript | 1 + 8 files changed, 1149 insertions(+), 10 deletions(-) rename lib/{dns.js => dns_legacy.js} (100%) create mode 100644 lib/dns_uv.js create mode 100644 src/cares_wrap.cc create mode 100644 test/internet/test-dns.js diff --git a/lib/dns.js b/lib/dns_legacy.js 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 index 0000000000..7a313a7666 --- /dev/null +++ b/lib/dns_uv.js @@ -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 index 0000000000..3f610af61a --- /dev/null +++ b/src/cares_wrap.cc @@ -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 +#include +#include + +#if defined(__OpenBSD__) || defined(__MINGW32__) +# include +#else +# include +#endif + +// Temporary hack: libuv should provide uv_inet_pton and uv_inet_ntop. +#ifdef __MINGW32__ + extern "C" { +# include +# include + } +# define uv_inet_pton ares_inet_pton +# define uv_inet_ntop ares_inet_ntop + +#else // __POSIX__ +# include +# 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 onanswer_sym; + +static ares_channel ares_channel; + + +static Local HostentToAddresses(struct hostent* host) { + HandleScope scope; + Local 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 address = String::New(ip); + addresses->Set(Integer::New(i), address); + } + + return scope.Close(addresses); +} + + +static Local HostentToNames(struct hostent* host) { + HandleScope scope; + Local names = Array::New(); + + for (int i = 0; host->h_aliases[i]; ++i) { + Local 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 key = String::NewSymbol("errno"); + Handle value = String::NewSymbol(AresErrnoString(errorno)); + Context::GetCurrent()->Global()->Set(key, value); +} + + +class QueryWrap { + public: + QueryWrap() { + HandleScope scope; + + object_ = Persistent::New(Object::New()); + } + + ~QueryWrap() { + assert(!object_.IsEmpty()); + + object_->DeleteHiddenValue(onanswer_sym); + + object_.Dispose(); + object_.Clear(); + } + + Handle GetObject() { + return object_; + } + + void SetOnAnswer(Handle 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(this); + } + + static void Callback(void *arg, int status, int timeouts, + unsigned char* answer_buf, int answer_len) { + QueryWrap* wrap = reinterpret_cast(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(arg); + + if (status != ARES_SUCCESS) { + wrap->ParseError(status); + } else { + wrap->Parse(host); + } + + delete wrap; + } + + Handle GetOnAnswer() { + HandleScope scope; + assert(!object_.IsEmpty()); + Handle onanswer = object_->GetHiddenValue(onanswer_sym); + assert(onanswer->IsFunction()); + return scope.Close(Handle::Cast(onanswer)); + } + + void CallOnAnswer(Local answer) { + HandleScope scope; + Local argv[2] = { Integer::New(0), answer }; + GetOnAnswer()->Call(this->object_, 2, argv); + } + + void CallOnAnswer(Local answer, Local family) { + HandleScope scope; + Local 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 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_; +}; + + +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 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 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 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 mx_records = Array::New(); + Local exchange_symbol = String::NewSymbol("exchange"); + Local 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 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 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 srv_records = Array::New(); + Local host_symbol = String::NewSymbol("host"); + Local port_symbol = String::NewSymbol("port"); + Local priority_symbol = String::NewSymbol("priority"); + Local 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 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 addresses = HostentToAddresses(host); + Local family = Integer::New(host->h_addrtype); + + this->CallOnAnswer(addresses, family); + } +}; + + +template +static Handle 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 = Local::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 +static Handle 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 = Local::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 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); + NODE_SET_METHOD(target, "queryAaaa", Query); + NODE_SET_METHOD(target, "queryCname", Query); + NODE_SET_METHOD(target, "queryMx", Query); + NODE_SET_METHOD(target, "queryNs", Query); + NODE_SET_METHOD(target, "querySrv", Query); + NODE_SET_METHOD(target, "getHostByAddr", Query); + NODE_SET_METHOD(target, "getHostByName", QueryWithFamily); + + 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::New(String::NewSymbol("onanswer")); +} + + +} // namespace cares_wrap + +} // namespace node + +NODE_MODULE(node_cares_wrap, node::cares_wrap::Initialize); diff --git a/src/node.js b/src/node.js index 50205c74af..8be85945ac 100644 --- a/src/node.js +++ b/src/node.js @@ -390,6 +390,9 @@ case 'timers': return process.useUV ? 'timers_uv' : 'timers_legacy'; + case 'dns': + return process.useUV ? 'dns_uv' : 'dns_legacy'; + default: return id; } diff --git a/src/node_extensions.h b/src/node_extensions.h index a001584d7c..57d4f8d606 100644 --- a/src/node_extensions.h +++ b/src/node_extensions.h @@ -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 index 0000000000..b833a35e1f --- /dev/null +++ b/test/internet/test-dns.js @@ -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'); +}); */ diff --git a/test/simple/test-c-ares.js b/test/simple/test-c-ares.js index 2bf3a11856..03e5413d34 100644 --- a/test/simple/test-c-ares.js +++ b/test/simple/test-c-ares.js @@ -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 e85ac1524f..1e35c0f4fe 100644 --- 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"):