Browse Source

c-ares based dns module

v0.7.4-release
Krishna Rajendran 15 years ago
committed by Ryan Dahl
parent
commit
dc1f4ebd44
  1. 121
      lib/dns_cares.js
  2. 11
      src/node.cc
  3. 660
      src/node_cares.cc
  4. 16
      src/node_cares.h
  5. 1
      wscript

121
lib/dns_cares.js

@ -0,0 +1,121 @@
var dns = process.binding('cares');
var watchers = {};
var activeWatchers = {};
var timer = new process.Timer();
timer.callback = function () {
for (var socket in activeWatchers) {
var s = parseInt(socket);
channel.processFD( watchers[socket].read ? s : dns.SOCKET_BAD
, watchers[socket].write ? s : dns.SOCKET_BAD
);
}
updateTimer();
}
function updateTimer() {
timer.stop();
for (var socket in activeWatchers) { // if !empty(activeWatchers)
var max = 20000;
var timeout = channel.timeout(max);
timer.start(timeout, 0);
return;
}
}
var channel = new dns.Channel({SOCK_STATE_CB: function (socket, read, write) {
var watcher;
if (socket in watchers) {
watcher = watchers[socket].watcher;
} else {
watcher = new process.IOWatcher();
watchers[socket] = { read: read
, write: write
, watcher: watcher
};
watcher.callback = function(read, write) {
channel.processFD( read ? socket : dns.SOCKET_BAD
, write ? socket : dns.SOCKET_BAD
);
updateTimer();
}
}
watcher.set(socket, read == 1, write == 1);
if (!(read || write)) {
watcher.stop();
delete activeWatchers[socket];
return;
} else {
watcher.start();
activeWatchers[socket] = watcher;
}
updateTimer();
}});
exports.resolve = function (domain, type_, callback_) {
var type, callback;
if (typeof(type_) == 'string') {
type = type_;
callback = callback_;
} else {
type = 'A';
callback = arguments[1];
}
var resolveFunc = resolveMap[type];
if (typeof(resolveFunc) == 'function') {
resolveFunc(domain, callback);
} else {
throw new Error('Unknown type "' + type + '"');
}
}
exports.resolve4 = function(domain, callback) { channel.query(domain, dns.A, callback) };
exports.resolve6 = function(domain, callback) { channel.query(domain, dns.AAAA, callback) };
exports.resolveTxt = function(domain, callback) { channel.query(domain, dns.TXT, callback) };
exports.resolveSrv = function(domain, callback) { channel.query(domain, dns.SRV, callback) };
exports.reverse = function(domain, callback) { channel.query(domain, dns.PTR, callback) };
exports.resolveNs = function(domain, callback) { channel.query(domain, dns.NS, callback) };
var resolveMap = {
'A' : exports.resolve4,
'AAAA': exports.resolve6,
'TXT' : exports.resolveTxt,
'SRV' : exports.resolveSrv,
'PTR' : exports.resolvePtr,
'NS' : exports.resolveNs,
};
// ERROR CODES
exports.NODATA = dns.NODATA;
exports.FORMERR = dns.FORMERR;
exports.BADRESP = dns.BADRESP;
exports.NOTFOUND = dns.NOTFOUND;
exports.BADNAME = dns.BADNAME;
exports.TIMEOUT = dns.TIMEOUT;
exports.CONNREFUSED = dns.CONNREFUSED;
exports.NOMEM = dns.NOMEM;
exports.DESTRUCTION = dns.DESTRUCTION;
exports.NOTIMP = dns.NOTIMP;
exports.EREFUSED = dns.EREFUSED;
exports.SERVFAIL = dns.SERVFAIL;

11
src/node.cc

@ -19,6 +19,7 @@
#include <node_net2.h>
#include <node_events.h>
#include <node_dns.h>
#include <node_cares.h>
#include <node_net.h>
#include <node_file.h>
#include <node_idle_watcher.h>
@ -1194,6 +1195,15 @@ static Handle<Value> Binding(const Arguments& args) {
binding_cache->Set(module, exports);
}
} else if (!strcmp(*module_v, "cares")) {
if (binding_cache->Has(module)) {
exports = binding_cache->Get(module)->ToObject();
} else {
exports = Object::New();
Cares::Initialize(exports);
binding_cache->Set(module, exports);
}
} else if (!strcmp(*module_v, "fs")) {
if (binding_cache->Has(module)) {
exports = binding_cache->Get(module)->ToObject();
@ -1266,6 +1276,7 @@ static Handle<Value> Binding(const Arguments& args) {
exports->Set(String::New("buffer"), String::New(native_buffer));
exports->Set(String::New("child_process"),String::New(native_child_process));
exports->Set(String::New("dns"), String::New(native_dns));
exports->Set(String::New("dns_cares"), String::New(native_dns_cares));
exports->Set(String::New("events"), String::New(native_events));
exports->Set(String::New("file"), String::New(native_file));
exports->Set(String::New("fs"), String::New(native_fs));

660
src/node_cares.cc

@ -0,0 +1,660 @@
#include <node_cares.h>
#include <node.h>
#include <v8.h>
#include <ares.h>
#include <arpa/nameser.h>
#include <arpa/inet.h>
namespace node {
using namespace v8;
class Channel : public ObjectWrap {
public:
static void Initialize(Handle<Object> target);
private:
static Persistent<FunctionTemplate> constructor_template;
static Handle<Value> New(const Arguments& args);
static Handle<Value> Query(const Arguments& args);
static Handle<Value> GetHostByName(const Arguments& args);
static Handle<Value> GetHostByAddr(const Arguments& args);
static Handle<Value> Timeout(const Arguments& args);
static Handle<Value> ProcessFD(const 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);
};
// To be passed to the QueryCb callback when a Query is finished.
// Holds a C callback to parse the response and the final JS callback
struct QueryArg {
typedef void (*ParseAnswerCb)(QueryArg*, unsigned char*, int);
Persistent<Function> js_cb;
ParseAnswerCb parse_cb;
QueryArg(const Local<Value> &js_cb, ParseAnswerCb parse_cb)
: js_cb(Persistent<Function>::New(Local<Function>::Cast(js_cb)))
, parse_cb(parse_cb) {}
~QueryArg() {
js_cb.Dispose();
}
};
Persistent<FunctionTemplate> Channel::constructor_template;
static Persistent<String> errno_symbol;
static Persistent<String> priority_symbol;
static Persistent<String> weight_symbol;
static Persistent<String> port_symbol;
static Persistent<String> name_symbol;
static Persistent<String> callback_symbol;
void Cares::Initialize(Handle<Object> target) {
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(String::NewSymbol("SOCKET_BAD"), Integer::New(ARES_SOCKET_BAD));
errno_symbol = NODE_PSYMBOL("errno");
priority_symbol = NODE_PSYMBOL("priority");
weight_symbol = NODE_PSYMBOL("weight");
port_symbol = NODE_PSYMBOL("port");
name_symbol = NODE_PSYMBOL("name");
target->Set(String::NewSymbol("AF_INET"), Integer::New(AF_INET));
target->Set(String::NewSymbol("AF_INET6"), Integer::New(AF_INET6));
target->Set(String::NewSymbol("A"), Integer::New(ns_t_a));
target->Set(String::NewSymbol("AAAA"), Integer::New(ns_t_aaaa));
target->Set(String::NewSymbol("NS"), Integer::New(ns_t_ns));
target->Set(String::NewSymbol("PTR"), Integer::New(ns_t_ptr));
target->Set(String::NewSymbol("TXT"), Integer::New(ns_t_txt));
target->Set(String::NewSymbol("SRV"), Integer::New(ns_t_srv));
target->Set(String::NewSymbol("NODATA"), Integer::New(ARES_ENODATA));
target->Set(String::NewSymbol("FORMERR"), Integer::New(ARES_EFORMERR));
target->Set(String::NewSymbol("BADRESP"), Integer::New(ARES_EBADRESP));
target->Set(String::NewSymbol("NOTFOUND"), Integer::New(ARES_ENOTFOUND));
target->Set(String::NewSymbol("BADNAME"), Integer::New(ARES_EBADNAME));
target->Set(String::NewSymbol("TIMEOUT"), Integer::New(ARES_ETIMEOUT));
target->Set(String::NewSymbol("CONNREFUSED"), Integer::New(ARES_ECONNREFUSED));
target->Set(String::NewSymbol("NOMEM"), Integer::New(ARES_ENOMEM));
target->Set(String::NewSymbol("DESTRUCTION"), Integer::New(ARES_EDESTRUCTION));
// Only occur if the ARES_FLAG_NOCHECKRESP flag was specified
target->Set(String::NewSymbol("NOTIMP"), Integer::New(ARES_ENOTIMP));
target->Set(String::NewSymbol("EREFUSED"), Integer::New(ARES_EREFUSED));
target->Set(String::NewSymbol("SERVFAIL"), Integer::New(ARES_ESERVFAIL));
Channel::Initialize(target);
}
static Local<Array> HostEntToAddresses(struct hostent* hostent) {
Local<Array> addresses = Array::New();
char ip[INET6_ADDRSTRLEN];
for (int i = 0; hostent->h_addr_list[i]; ++i) {
inet_ntop(hostent->h_addrtype, hostent->h_addr_list[i], ip, sizeof(ip));
Local<String> address = String::New(ip);
addresses->Set(Integer::New(i), address);
}
return addresses;
}
static Local<Array> HostEntToNames(struct hostent* hostent) {
Local<Array> names = Array::New();
for (int i = 0; hostent->h_aliases[i]; ++i) {
Local<String> address = String::New(hostent->h_aliases[i]);
names->Set(Integer::New(i), address);
}
return names;
}
static void ResolveError(Persistent<Function> &cb, int status) {
HandleScope scope;
Local<Value> e = Exception::Error(String::NewSymbol(ares_strerror(status)));
Local<Object> obj = e->ToObject();
obj->Set(errno_symbol, Integer::New(status));
TryCatch try_catch;
cb->Call(Context::GetCurrent()->Global(), 1, &e);
if (try_catch.HasCaught()) {
FatalException(try_catch);
}
}
static void HostByNameCb(void *data,
int status,
int timeouts,
struct hostent *hostent) {
HandleScope scope;
Persistent<Function> *cb = cb_unwrap(data);
if (status != ARES_SUCCESS) {
ResolveError(*cb, status);
cb_destroy(cb);
return;
}
TryCatch try_catch;
Local<Array> addresses = HostEntToAddresses(hostent);
Local<Value> argv[2] = { Local<Value>::New(Null()), addresses};
(*cb)->Call(Context::GetCurrent()->Global(), 2, argv);
if (try_catch.HasCaught()) {
FatalException(try_catch);
}
cb_destroy(cb);
}
static void HostByAddrCb(void *data,
int status,
int timeouts,
struct hostent *hostent) {
HandleScope scope;
Persistent<Function> *cb = cb_unwrap(data);
if (status != ARES_SUCCESS) {
ResolveError(*cb, status);
cb_destroy(cb);
return;
}
TryCatch try_catch;
Local<Array> names = HostEntToNames(hostent);
Local<Value> argv[2] = { Local<Value>::New(Null()), names };
(*cb)->Call(Context::GetCurrent()->Global(), 2, argv);
if (try_catch.HasCaught()) {
FatalException(try_catch);
}
cb_destroy(cb);
}
static void cb_call(Persistent<Function> &cb, int argc, Local<Value> *argv) {
TryCatch try_catch;
cb->Call(Context::GetCurrent()->Global(), argc, argv);
if (try_catch.HasCaught()) {
FatalException(try_catch);
}
}
static void ParseAnswerA(QueryArg *arg, unsigned char* abuf, int alen) {
HandleScope scope;
hostent* host;
int status = ares_parse_a_reply(abuf, alen, &host, NULL, NULL);
if (status != ARES_SUCCESS) {
ResolveError(arg->js_cb, status);
return;
}
Local<Array> addresses = HostEntToAddresses(host);
ares_free_hostent(host);
Local<Value> argv[2] = { Local<Value>::New(Null()), addresses };
cb_call(arg->js_cb, 2, argv);
}
static void ParseAnswerAAAA(QueryArg *arg, unsigned char* abuf, int alen) {
HandleScope scope;
hostent* host;
int status = ares_parse_aaaa_reply(abuf, alen, &host, NULL, NULL);
if (status != ARES_SUCCESS) {
ResolveError(arg->js_cb, status);
return;
}
Local<Array> addresses = HostEntToAddresses(host);
ares_free_hostent(host);
Local<Value> argv[2] = { Local<Value>::New(Null()), addresses};
cb_call(arg->js_cb, 2, argv);
}
static void ParseAnswerNS(QueryArg *arg, unsigned char* abuf, int alen) {
HandleScope scope;
hostent* host;
int status = ares_parse_ns_reply(abuf, alen, &host);
if (status != ARES_SUCCESS) {
ResolveError(arg->js_cb, status);
return;
}
Local<Array> names = HostEntToNames(host);
ares_free_hostent(host);
Local<Value> argv[2] = { Local<Value>::New(Null()), names };
cb_call(arg->js_cb, 2, argv);
}
static void ParseAnswerSRV(QueryArg *arg, unsigned char* abuf, int alen) {
HandleScope scope;
struct ares_srv_reply *srv_out;
int status = ares_parse_srv_reply(abuf, alen, &srv_out);
if (status != ARES_SUCCESS) {
ResolveError(arg->js_cb, status);
return;
}
Local<Array> srv_records = Array::New();
struct ares_srv_reply *current = srv_out;
for (int i = 0; current; ++i, current = current->next) {
Local<Object> srv = Object::New();
srv->Set(priority_symbol, Integer::New(current->priority));
srv->Set(weight_symbol, Integer::New(current->weight));
srv->Set(port_symbol, Integer::New(current->port));
srv->Set(name_symbol, String::New(current->host));
srv_records->Set(Integer::New(i), srv);
}
ares_free_data(srv_out);
Local<Value> argv[2] = { Local<Value>::New(Null()), srv_records };
cb_call(arg->js_cb, 2, argv);
}
static void ParseAnswerTXT(QueryArg *arg, unsigned char* abuf, int alen) {
HandleScope scope;
struct ares_txt_reply *txt_out;
int status = ares_parse_txt_reply(abuf, alen, &txt_out);
if (status != ARES_SUCCESS) {
ResolveError(arg->js_cb, status);
return;
}
Local<Array> txt_records = Array::New();
struct ares_txt_reply *current = txt_out;
for (int i = 0; current; ++i, current = current->next) {
Local<String> txt = String::New(reinterpret_cast<char*>(current->txt));
txt_records->Set(Integer::New(i), txt);
}
ares_free_data(txt_out);
Local<Value> argv[2] = { Local<Value>::New(Null()), txt_records };
cb_call(arg->js_cb, 2, argv);
}
void Channel::QueryCb(void *arg,
int status,
int timeouts,
unsigned char* abuf,
int alen) {
QueryArg *query_arg = static_cast<QueryArg*>(arg);
HandleScope scope;
if (status != ARES_SUCCESS) {
ResolveError(query_arg->js_cb, status);
delete query_arg;
return;
}
query_arg->parse_cb(query_arg, abuf, alen);
delete query_arg;
}
void Channel::Initialize(Handle<Object> target) {
HandleScope scope;
Local<FunctionTemplate> t = FunctionTemplate::New(Channel::New);
constructor_template = Persistent<FunctionTemplate>::New(t);
constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
constructor_template->SetClassName(String::NewSymbol("Channel"));
NODE_SET_PROTOTYPE_METHOD(constructor_template, "getHostByName", Channel::GetHostByName);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "getHostByAddr", Channel::GetHostByAddr);
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);
target->Set(String::NewSymbol("Channel"), constructor_template->GetFunction());
callback_symbol = NODE_PSYMBOL("callback");
}
Handle<Value> Channel::New(const Arguments& args) {
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 ThrowException(Exception::TypeError(
String::New("Bad Options Argument")));
}
Local<Object> options_o = Local<Object>::Cast(args[0]);
Local<Array> keys = options_o->GetPropertyNames();
int length = keys->Length();
for (int i = 0; i < length; ++i) {
Local<String> opt = Local<String>::Cast(keys->Get(Integer::New(i)));
if (opt->Equals(String::New("SOCK_STATE_CB"))) {
c->handle_->Set(callback_symbol, options_o->Get(opt));
options.sock_state_cb_data = c;
options.sock_state_cb = Channel::SockStateCb;
optmask |= ARES_OPT_SOCK_STATE_CB;
continue;
}
return ThrowException(Exception::Error(
String::New("Unknown Option")));
}
}
ares_init_options(&c->channel, &options, optmask);
return args.This();
}
Handle<Value> Channel::Query(const Arguments& args) {
HandleScope scope;
Channel *c = ObjectWrap::Unwrap<Channel>(args.Holder());
assert(c);
if (!args[0]->IsString()) {
return ThrowException(Exception::TypeError(
String::New("First argument must be a name")));
}
if (!args[1]->IsInt32()) {
return ThrowException(Exception::TypeError(
String::New("Second argument must be a query type")));
}
if (!args[2]->IsFunction()) {
return ThrowException(Exception::TypeError(
String::New("Third argument must be a callback")));
}
String::Utf8Value name(args[0]->ToString());
int type = args[1]->Int32Value();
QueryArg::ParseAnswerCb parse_cb;
switch(type) {
case ns_t_a:
parse_cb = ParseAnswerA;
break;
case ns_t_aaaa:
parse_cb = ParseAnswerAAAA;
break;
case ns_t_ns:
parse_cb = ParseAnswerNS;
break;
case ns_t_txt:
parse_cb = ParseAnswerTXT;
break;
case ns_t_srv:
parse_cb = ParseAnswerSRV;
break;
case ns_t_ptr:
int length, family;
char address_b[sizeof(struct in6_addr)];
if (inet_pton(AF_INET, *name, &address_b) == 1) {
length = sizeof(struct in_addr);
family = AF_INET;
} else if (inet_pton(AF_INET6, *name, &address_b) == 1) {
length = sizeof(struct in6_addr);
family = AF_INET6;
} else {
return ThrowException(Exception::Error(String::New("Invalid IP address")));
}
ares_gethostbyaddr(c->channel, address_b, length, family, HostByAddrCb, cb_persist(args[2]));
return Undefined();
default:
return ThrowException(Exception::Error(
String::New("Unsupported query type")));
}
QueryArg *query_arg = new QueryArg(args[2], parse_cb);
ares_query(c->channel, *name, ns_c_in, type, QueryCb, query_arg);
return Undefined();
}
Handle<Value> Channel::GetHostByAddr(const Arguments& args) {
HandleScope scope;
Channel *c = ObjectWrap::Unwrap<Channel>(args.Holder());
assert(c);
if (!args[0]->IsString()) {
return ThrowException(Exception::Error(
String::New("First argument must be a address")));
}
if (!args[1]->IsInt32()) {
return ThrowException(Exception::Error(
String::New("Second argument must be an address family")));
}
if (!args[2]->IsFunction()) {
return ThrowException(Exception::Error(
String::New("Third argument must be a callback")));
}
int family = args[1]->Int32Value();
if (family != AF_INET6 && family != AF_INET) {
return ThrowException(Exception::Error(
String::New("Unsupported address family")));
}
String::Utf8Value address_s(args[0]->ToString());
char address_b[sizeof(struct in6_addr)];
int r = inet_pton(family, *address_s, address_b);
if (r != 1) {
return ThrowException(Exception::Error(
String::New("Invalid network address")));
}
int length;
if (family == AF_INET6)
length = sizeof(struct in6_addr);
else
length = sizeof(struct in_addr);
ares_gethostbyaddr(c->channel, address_b, length, family, HostByAddrCb, cb_persist(args[2]));
return Undefined();
}
Handle<Value> Channel::GetHostByName(const Arguments& args) {
HandleScope scope;
Channel *c = ObjectWrap::Unwrap<Channel>(args.Holder());
assert(c);
if (!args[0]->IsString()) {
return ThrowException(Exception::Error(
String::New("First argument must be a name")));
}
if (!args[1]->IsInt32()) {
return ThrowException(Exception::Error(
String::New("Second argument must be a family")));
}
if (!args[2]->IsFunction()) {
return ThrowException(Exception::Error(
String::New("Third argument must be a callback")));
}
int family = args[1]->Int32Value();
if (family != AF_INET6 && family != AF_INET) {
return ThrowException(Exception::Error(
String::New("Unsupported address family")));
}
String::Utf8Value name(args[0]->ToString());
ares_gethostbyname(c->channel, *name, family, HostByNameCb, cb_persist(args[2]));
return Undefined();
}
Handle<Value> Channel::Timeout(const Arguments& args) {
HandleScope scope;
Channel *c = ObjectWrap::Unwrap<Channel>(args.Holder());
assert(c);
if (!args[0]->IsInt32()) {
return ThrowException(Exception::Error(
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, &maxtv, &tvbuf);
return scope.Close(Integer::New(ret->tv_sec * 1000 + ret->tv_usec / 1000));
}
Handle<Value> Channel::ProcessFD(const Arguments& args) {
HandleScope scope;
Channel *c = ObjectWrap::Unwrap<Channel>(args.Holder());
assert(c);
int read_fd, write_fd;
if (!args[0]->IsInt32()) {
return ThrowException(Exception::Error(
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 ThrowException(Exception::Error(
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 Undefined();
}
void Channel::SockStateCb(void *data, int sock, int read, int write) {
Channel *c = static_cast<Channel*>(data);
HandleScope scope;
Local<Value> callback_v = c->handle_->Get(callback_symbol);
if (!callback_v->IsFunction()) return;
Local<Function> callback = Local<Function>::Cast(callback_v);
Local<Value> argv[3];
argv[0] = Integer::New(sock);
argv[1] = Integer::New(read);
argv[2] = Integer::New(write);
TryCatch try_catch;
callback->Call(c->handle_, 3, argv);
if (try_catch.HasCaught()) {
FatalException(try_catch);
}
}
} // namespace node

16
src/node_cares.h

@ -0,0 +1,16 @@
#ifndef NODE_CARES_H_
#define NODE_CARES_H_
#include <node.h>
#include <v8.h>
#include <ares.h>
namespace node {
class Cares {
public:
static void Initialize(v8::Handle<v8::Object> target);
};
} // namespace node
#endif // NODE_CARES_H_

1
wscript

@ -416,6 +416,7 @@ def build(bld):
src/node_child_process.cc
src/node_constants.cc
src/node_dns.cc
src/node_cares.cc
src/node_events.cc
src/node_file.cc
src/node_http.cc

Loading…
Cancel
Save