Skip to content

Instantly share code, notes, and snippets.

@piscisaureus
Created July 3, 2011 19:54
Show Gist options
  • Save piscisaureus/1062552 to your computer and use it in GitHub Desktop.
Save piscisaureus/1062552 to your computer and use it in GitHub Desktop.
cares_wrap.cc
// 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