Created
July 3, 2011 19:54
-
-
Save piscisaureus/1062552 to your computer and use it in GitHub Desktop.
cares_wrap.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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::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 ares_channel ares_channel_; | |
static Local<Array> hostent_to_addresses(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> hostent_to_names(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* ares_errno_string(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 set_ares_errno(int errorno) { | |
HandleScope scope; | |
Handle<Value> key = String::NewSymbol("errno"); | |
Handle<Value> value = String::NewSymbol(ares_errno_string(errorno)); | |
Context::GetCurrent()->Global()->Set(key, value); | |
} | |
class CaresQueryWrap { | |
public: | |
Handle<Object> GetObject() { | |
return object_; | |
} | |
// Subclasses should implement this. | |
virtual int Send(const char* name) = 0; | |
CaresQueryWrap() { | |
HandleScope scope; | |
object_ = Persistent<Object>::New(Object::New()); | |
} | |
~CaresQueryWrap() { | |
assert(!object_.IsEmpty()); | |
object_.Dispose(); | |
object_.Clear(); | |
} | |
protected: | |
void* GetQueryArg() { | |
return static_cast<void*>(this); | |
} | |
static void Callback(void *arg, int status, int timeouts, unsigned char* answer_buf, int answer_len) { | |
CaresQueryWrap* wrap = reinterpret_cast<CaresQueryWrap*>(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) { | |
CaresQueryWrap* wrap = reinterpret_cast<CaresQueryWrap*>(arg); | |
if (status != ARES_SUCCESS) { | |
wrap->ParseError(status); | |
} else { | |
wrap->Parse(host); | |
} | |
delete wrap; | |
} | |
void MakeAnswerCallback(Local<Value> answer) { | |
HandleScope scope; | |
assert(!object_.IsEmpty()); | |
Local<Value> argv[2] = { Integer::New(0), answer }; | |
node::MakeCallback(this->object_, "onanswer", 2, argv); | |
} | |
void ParseError(int status) { | |
HandleScope scope; | |
assert(status != ARES_SUCCESS); | |
assert(!object_.IsEmpty()); | |
set_ares_errno(status); | |
Local<Value> argv[1] = { Integer::New(0) }; | |
node::MakeCallback(this->object_, "onanswer", 1, argv); | |
} | |
// Subclasses should implement this. | |
virtual void Parse(unsigned char* buf, int len) { | |
assert(0); | |
}; | |
// Subclasses should implement this. | |
virtual void Parse(struct hostent* host) { | |
assert(0); | |
}; | |
private: | |
Persistent<Object> object_; | |
}; | |
class CaresAQueryWrap: public CaresQueryWrap { | |
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 = hostent_to_addresses(host); | |
ares_free_hostent(host); | |
this->MakeAnswerCallback(addresses); | |
} | |
}; | |
class CaresAaaaQueryWrap: public CaresQueryWrap { | |
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 = hostent_to_addresses(host); | |
ares_free_hostent(host); | |
this->MakeAnswerCallback(addresses); | |
} | |
}; | |
class CaresCnameQueryWrap: public CaresQueryWrap { | |
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->MakeAnswerCallback(result); | |
} | |
}; | |
class CaresMxQueryWrap: public CaresQueryWrap { | |
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->MakeAnswerCallback(mx_records); | |
} | |
}; | |
class CaresNsQueryWrap: public CaresQueryWrap { | |
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 = hostent_to_names(host); | |
ares_free_hostent(host); | |
this->MakeAnswerCallback(names); | |
} | |
}; | |
class CaresPtrQueryWrap: public CaresQueryWrap { | |
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->MakeAnswerCallback(hostent_to_names(host)); | |
} | |
}; | |
class CaresSrvQueryWrap: public CaresQueryWrap { | |
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->MakeAnswerCallback(srv_records); | |
} | |
}; | |
template <class Wrap> | |
static Handle<Value> Query(const Arguments& args) { | |
HandleScope scope; | |
Wrap* wrap = new Wrap(); | |
assert(!args.IsConstructCall()); | |
String::Utf8Value name(args[0]->ToString()); | |
int r = wrap->Send(*name); | |
if (r) { | |
set_ares_errno(r); | |
delete wrap; | |
return scope.Close(v8::Null()); | |
} else { | |
return scope.Close(wrap->GetObject()); | |
} | |
} | |
static void Initialize(Handle<Object> target) { | |
HandleScope scope; | |
struct ares_options options; | |
int r; | |
r = ares_library_init(ARES_LIB_INIT_ALL); | |
assert(r == ARES_SUCCESS); | |
uv_ares_init_options(&ares_channel_, &options, 0); | |
assert(r == 0); | |
NODE_SET_METHOD(target, "queryA", Query<CaresAQueryWrap>); | |
NODE_SET_METHOD(target, "queryAaaa", Query<CaresAaaaQueryWrap>); | |
NODE_SET_METHOD(target, "queryCname", Query<CaresCnameQueryWrap>); | |
NODE_SET_METHOD(target, "queryMx", Query<CaresMxQueryWrap>); | |
NODE_SET_METHOD(target, "queryNs", Query<CaresNsQueryWrap>); | |
NODE_SET_METHOD(target, "queryPtr", Query<CaresPtrQueryWrap>); | |
NODE_SET_METHOD(target, "querySrv", Query<CaresSrvQueryWrap>); | |
} | |
} // namespace cares_wrap | |
} // namespace node | |
NODE_MODULE(node_cares_wrap, node::cares_wrap::Initialize); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment