370 line
10 KiB
C++
370 line
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);
|
|
}
|