veriname/ares/ares.cc

370 lines
10 KiB
C++

#include "dns_parser.h"
#include <node.h>
#include <v8.h>
#include <ares.h>
#include <ares_dns.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <arpa/nameser.h>
#include <arpa/inet.h>
#ifdef __OpenBSD__
# ifndef ns_t_a
# include <nameser.h>
# endif
#endif // __OpenBSD__
namespace ares {
class Channel : public node::ObjectWrap {
public:
Channel();
virtual ~Channel();
static void Initialize(v8::Handle<v8::Object> target);
private:
static v8::Persistent<v8::FunctionTemplate> constructor_template;
static v8::Handle<v8::Value> New(const v8::Arguments& args);
static v8::Handle<v8::Value> Query(const v8::Arguments& args);
static v8::Handle<v8::Value> SetServers(const v8::Arguments& args);
static v8::Handle<v8::Value> Timeout(const v8::Arguments& args);
static v8::Handle<v8::Value> ProcessFD(const v8::Arguments& args);
ares_channel channel;
static void SockStateCb(void *data, int sock, int read, int write);
static void QueryCb(void *arg, int status, int timeouts, unsigned char* abuf, int alen);
struct ares_addr_node *servers;
};
v8::Persistent<v8::FunctionTemplate> Channel::constructor_template;
static v8::Persistent<v8::String> callback_symbol;
static void Initialize(v8::Handle<v8::Object> target) {
v8::HandleScope scope;
int r = ares_library_init(ARES_LIB_INIT_ALL);
if (0 != r) {
// TODO
// ThrowException(Exception::Error(String::New(ares_strerror(r))));
assert(r == 0);
}
target->Set(v8::String::NewSymbol("SOCKET_BAD"), v8::Integer::New(ARES_SOCKET_BAD));
target->Set(v8::String::NewSymbol("AF_INET"), v8::Integer::New(AF_INET));
target->Set(v8::String::NewSymbol("AF_INET6"), v8::Integer::New(AF_INET6));
target->Set(v8::String::NewSymbol("NODATA"), v8::Integer::New(ARES_ENODATA));
target->Set(v8::String::NewSymbol("FORMERR"), v8::Integer::New(ARES_EFORMERR));
target->Set(v8::String::NewSymbol("BADRESP"), v8::Integer::New(ARES_EBADRESP));
target->Set(v8::String::NewSymbol("NOTFOUND"), v8::Integer::New(ARES_ENOTFOUND));
target->Set(v8::String::NewSymbol("BADNAME"), v8::Integer::New(ARES_EBADNAME));
target->Set(v8::String::NewSymbol("TIMEOUT"), v8::Integer::New(ARES_ETIMEOUT));
target->Set(v8::String::NewSymbol("CONNREFUSED"), v8::Integer::New(ARES_ECONNREFUSED));
target->Set(v8::String::NewSymbol("NOMEM"), v8::Integer::New(ARES_ENOMEM));
target->Set(v8::String::NewSymbol("DESTRUCTION"), v8::Integer::New(ARES_EDESTRUCTION));
// Only occur if the ARES_FLAG_NOCHECKRESP flag was specified
target->Set(v8::String::NewSymbol("NOTIMP"), v8::Integer::New(ARES_ENOTIMP));
target->Set(v8::String::NewSymbol("EREFUSED"), v8::Integer::New(ARES_EREFUSED));
target->Set(v8::String::NewSymbol("SERVFAIL"), v8::Integer::New(ARES_ESERVFAIL));
Channel::Initialize(target);
}
void Channel::Initialize(v8::Handle<v8::Object> target) {
v8::HandleScope scope;
v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(Channel::New);
constructor_template = v8::Persistent<v8::FunctionTemplate>::New(t);
constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
constructor_template->SetClassName(v8::String::NewSymbol("Channel"));
using namespace v8;
NODE_SET_PROTOTYPE_METHOD(constructor_template, "query", Channel::Query);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "timeout", Channel::Timeout);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "processFD", Channel::ProcessFD);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "setServers", Channel::SetServers);
target->Set(v8::String::NewSymbol("Channel"), constructor_template->GetFunction());
callback_symbol = NODE_PSYMBOL("callback");
}
Channel::Channel() : servers(0) {
}
Channel::~Channel() {
delete [] servers;
}
v8::Handle<v8::Value> Channel::New(const v8::Arguments& args) {
v8::HandleScope scope;
struct ares_options options;
int optmask = 0;
Channel *c = new Channel();
c->Wrap(args.This());
if (args.Length() > 0) {
if(!args[0]->IsObject()) {
return v8::ThrowException(v8::Exception::TypeError(
v8::String::New("Bad Options Argument")));
}
v8::Local<v8::Object> options_o = v8::Local<v8::Object>::Cast(args[0]);
v8::Local<v8::Value> cb = options_o->Get(v8::String::NewSymbol("SOCK_STATE_CB"));
if (!cb.IsEmpty()) {
c->handle_->Set(callback_symbol, cb);
options.sock_state_cb_data = c;
options.sock_state_cb = Channel::SockStateCb;
optmask |= ARES_OPT_SOCK_STATE_CB;
}
}
ares_init_options(&c->channel, &options, optmask);
return args.This();
}
v8::Handle<v8::Value> Channel::Query(const v8::Arguments& args) {
v8::HandleScope scope;
Channel *c = node::ObjectWrap::Unwrap<Channel>(args.Holder());
assert(c);
if (!args[0]->IsString()) {
return v8::ThrowException(v8::Exception::TypeError(
v8::String::New("First argument must be a name")));
}
if (!args[1]->IsInt32()) {
return v8::ThrowException(v8::Exception::TypeError(
v8::String::New("Second argument must be a query type!!")));
}
if (!args[2]->IsFunction()) {
return v8::ThrowException(v8::Exception::TypeError(
v8::String::New("Third argument must be a callback")));
}
v8::String::Utf8Value name(args[0]->ToString());
int type = args[1]->Int32Value();
ares_query(c->channel, *name, ns_c_in, type, QueryCb, node::cb_persist(args[2]));
return v8::Undefined();
}
v8::Handle<v8::Value> Channel::Timeout(const v8::Arguments& args) {
v8::HandleScope scope;
Channel *c = ObjectWrap::Unwrap<Channel>(args.Holder());
assert(c);
if (!args[0]->IsInt32()) {
return v8::ThrowException(v8::Exception::Error(
v8::String::New("First argument must be an integer number of milliseconds")));
}
struct timeval tvbuf, maxtv, *ret;
int64_t time = args[0]->IntegerValue();
maxtv.tv_sec = time/1000;
maxtv.tv_usec = (time % 1000) * 1000;
ret = ares_timeout(c->channel, (time > 0) ? &maxtv : NULL, &tvbuf);
return scope.Close(v8::Integer::New(ret ? ret->tv_sec * 1000 + ret->tv_usec / 1000 : -1));
}
v8::Handle<v8::Value> Channel::ProcessFD(const v8::Arguments& args) {
v8::HandleScope scope;
Channel *c = ObjectWrap::Unwrap<Channel>(args.Holder());
assert(c);
int read_fd, write_fd;
if (!args[0]->IsInt32()) {
return v8::ThrowException(v8::Exception::Error(
v8::String::New("First argument must be a file descriptor or SOCKET_BAD")));
}
read_fd = args[0]->Int32Value();
if (args.Length() > 1) {
if (!args[1]->IsInt32()) {
return v8::ThrowException(v8::Exception::Error(
v8::String::New("Second argument must be a file descriptor or SOCKET_BAD")));
}
write_fd = args[1]->Int32Value();
} else {
write_fd = ARES_SOCKET_BAD;
}
ares_process_fd(c->channel, read_fd, write_fd);
return v8::Undefined();
}
static bool parse_addr(v8::Handle<v8::Value> v, ares_addr_node &a) {
memset(&a, 0, sizeof(a));
if (!v->IsString()) return false;
v8::String::Utf8Value s(v->ToString());
// avoiding buffer overflows in the following strcat
// 2001:0db8:85a3:08d3:1319:8a2e:0370:7334
// 39 = max ipv6 address.
if (s.length() > INET6_ADDRSTRLEN) return false;
if (inet_pton(AF_INET, *s, &(a.addr.addr4)) > 0) {
a.family = AF_INET;
return true;
}
if (inet_pton(AF_INET6, *s, &(a.addr.addr6)) > 0) {
a.family = AF_INET6;
return true;
}
return false;
}
v8::Handle<v8::Value> Channel::SetServers(const v8::Arguments& args) {
int status;
v8::HandleScope scope;
Channel *c = node::ObjectWrap::Unwrap<Channel>(args.Holder());
assert(c);
if (args.Length() == 0) {
status = ares_set_servers(c->channel, NULL);
if (ARES_SUCCESS != status) return v8::ThrowException(dns_parser::AresException(status));
delete [] c->servers;
c->servers = 0;
return v8::Undefined();
}
if (args[0]->IsString()) {
ares_addr_node *servers = new ares_addr_node[args.Length()];
for (int i = 0, l = args.Length(); i < l; i++) {
if (i > 0) servers[i-1].next = &servers[i];
if (!parse_addr(args[i], servers[i])) {
return v8::ThrowException(v8::Exception::Error(
v8::String::New("Arguments must be strings with valid IP addresses")));
}
}
status = ares_set_servers(c->channel, servers);
if (ARES_SUCCESS != status) return v8::ThrowException(dns_parser::AresException(status));
return v8::Undefined();
}
if (args[0]->IsArray()) {
v8::Local<v8::Array> a = v8::Local<v8::Array>::Cast(args[0]);
if (args.Length() == 0) {
status = ares_set_servers(c->channel, NULL);
if (ARES_SUCCESS != status) return v8::ThrowException(dns_parser::AresException(status));
delete [] c->servers;
c->servers = 0;
return v8::Undefined();
}
ares_addr_node *servers = new ares_addr_node[args.Length()];
for (int i = 0, l = a->Length(); i < l; i++) {
if (i > 0) servers[i-1].next = &servers[i];
if (!parse_addr(a->Get(i), servers[i])) {
return v8::ThrowException(v8::Exception::Error(
v8::String::New("Arguments must be strings with valid IP addresses")));
}
}
status = ares_set_servers(c->channel, servers);
if (ARES_SUCCESS != status) return v8::ThrowException(dns_parser::AresException(status));
return v8::Undefined();
}
return v8::ThrowException(v8::Exception::Error(
v8::String::New("Arguments must be strings with valid IP addresses")));
}
void Channel::SockStateCb(void *data, int sock, int read, int write) {
Channel *c = static_cast<Channel*>(data);
v8::HandleScope scope;
v8::Local<v8::Value> callback_v = c->handle_->Get(callback_symbol);
if (!callback_v->IsFunction()) return;
v8::Local<v8::Function> callback = v8::Local<v8::Function>::Cast(callback_v);
v8::Local<v8::Value> argv[3];
argv[0] = v8::Integer::New(sock);
argv[1] = v8::Integer::New(read);
argv[2] = v8::Integer::New(write);
v8::TryCatch try_catch;
callback->Call(c->handle_, 3, argv);
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
}
void Channel::QueryCb(void *arg, int status, int timeouts, unsigned char* abuf, int alen) {
v8::HandleScope scope;
v8::Persistent<v8::Function> *cb = node::cb_unwrap(arg);
v8::Local<v8::Value> result;
if (status != ARES_SUCCESS && 0 == alen) {
result = dns_parser::AresException(status);
} else {
result = dns_parser::parse_dns_response(abuf, alen, &status);
}
v8::TryCatch try_catch;
if (status != ARES_SUCCESS) {
(*cb)->Call(v8::Context::GetCurrent()->Global(), 1, &result);
} else {
v8::Local<v8::Value> argv[2] = { v8::Local<v8::Value>::New(v8::Null()), result };
(*cb)->Call(v8::Context::GetCurrent()->Global(), 2, argv);
}
node::cb_destroy(cb);
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
}
} // namespace ares
extern "C" void
init (v8::Handle<v8::Object> target) {
v8::HandleScope scope;
ares::Initialize(target);
dns_parser::Initialize(target);
}