Blob Blame Raw

#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);
}