mirror of https://github.com/lukechilds/node.git
8 changed files with 1149 additions and 10 deletions
@ -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'; |
@ -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); |
@ -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'); |
|||
}); */ |
Loading…
Reference in new issue