commit 9213b14663fe7493d80d5259f1d48c43080a5acf Author: Stefan Bühler Date: Tue Dec 14 00:09:53 2010 +0100 Initial commit diff --git a/ares/.gitignore b/ares/.gitignore new file mode 100644 index 0000000..ce93513 --- /dev/null +++ b/ares/.gitignore @@ -0,0 +1,2 @@ +build +.lock-wscript diff --git a/ares/ares.cc b/ares/ares.cc new file mode 100644 index 0000000..dd6de37 --- /dev/null +++ b/ares/ares.cc @@ -0,0 +1,369 @@ + +#include "dns_parser.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#ifdef __OpenBSD__ +# ifndef ns_t_a +# include +# endif +#endif // __OpenBSD__ + +namespace ares { + +class Channel : public node::ObjectWrap { +public: + Channel(); + virtual ~Channel(); + + static void Initialize(v8::Handle target); + +private: + static v8::Persistent constructor_template; + + static v8::Handle New(const v8::Arguments& args); + static v8::Handle Query(const v8::Arguments& args); + static v8::Handle SetServers(const v8::Arguments& args); + static v8::Handle Timeout(const v8::Arguments& args); + static v8::Handle 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 Channel::constructor_template; + +static v8::Persistent callback_symbol; + + +static void Initialize(v8::Handle 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 target) { + v8::HandleScope scope; + + v8::Local t = v8::FunctionTemplate::New(Channel::New); + constructor_template = v8::Persistent::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 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 options_o = v8::Local::Cast(args[0]); + + v8::Local 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 Channel::Query(const v8::Arguments& args) { + v8::HandleScope scope; + Channel *c = node::ObjectWrap::Unwrap(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 Channel::Timeout(const v8::Arguments& args) { + v8::HandleScope scope; + Channel *c = ObjectWrap::Unwrap(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 Channel::ProcessFD(const v8::Arguments& args) { + v8::HandleScope scope; + Channel *c = ObjectWrap::Unwrap(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 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 Channel::SetServers(const v8::Arguments& args) { + int status; + + v8::HandleScope scope; + Channel *c = node::ObjectWrap::Unwrap(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 a = v8::Local::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(data); + v8::HandleScope scope; + + v8::Local callback_v = c->handle_->Get(callback_symbol); + if (!callback_v->IsFunction()) return; + v8::Local callback = v8::Local::Cast(callback_v); + + v8::Local 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 *cb = node::cb_unwrap(arg); + v8::Local 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 argv[2] = { v8::Local::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 target) { + v8::HandleScope scope; + ares::Initialize(target); + dns_parser::Initialize(target); +} diff --git a/ares/dns_parser.cc b/ares/dns_parser.cc new file mode 100644 index 0000000..07b5f7e --- /dev/null +++ b/ares/dns_parser.cc @@ -0,0 +1,586 @@ +#include "dns_parser.h" + +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#ifdef __OpenBSD__ +# ifndef ns_t_a +# include +# endif +#endif // __OpenBSD__ + +namespace dns_parser { + +static v8::Persistent question_symbol, answer_symbol, authority_symbol, additional_symbol; +static v8::Persistent type_symbol, ntype_symbol, class_symbol, nclass_symbol, ttl_symbol, rdata_symbol, text_symbol, data_symbol; +static v8::Persistent priority_symbol, weight_symbol, port_symbol, name_symbol, exchange_symbol, target_symbol; +static v8::Persistent mname_symbol, rname_symbol, serial_symbol, refresh_symbol, retry_symbol, expire_symbol, minimum_symbol; + +static v8::Persistent space_symbol; + +typedef int (*ParseRRData)(const unsigned char *abuf, int alen, const unsigned char *rr_data, int rr_len, v8::Local &r); + +struct RR_TYPE { + const char *s; + int n; + ParseRRData parse_in; /* only parse class IN */ + v8::Persistent symbol; +}; + +struct RR_CLASS { + const char *s; + int n; + v8::Persistent symbol; +}; + +static int parse_char_string(const unsigned char *&rr_data, int &rr_len, v8::Local &s) { + if (rr_len <= 0) return ARES_EBADRESP; + + int len = (unsigned int) rr_data[0]; + rr_data++; rr_len--; + if (len > rr_len) return ARES_EBADRESP; + + s = v8::String::New((const char*) rr_data, len); + rr_data += len; rr_len -= len; + + return ARES_SUCCESS; +} + +static int ParseRRData_A(const unsigned char *, int , const unsigned char *rr_data, int rr_len, v8::Local &r) { + if (rr_len == sizeof(struct in_addr)) { + struct in_addr a; + memcpy(&a, rr_data, sizeof(a)); + + char ip[INET_ADDRSTRLEN]; + ip[0] = '\0'; + ::inet_ntop(AF_INET, &a, ip, INET_ADDRSTRLEN); + + v8::Local address = v8::String::New(ip); + r->Set(text_symbol, address); + return ARES_SUCCESS; + } + return ARES_EBADRESP; +} + +static int ParseRRData_AAAA(const unsigned char *, int , const unsigned char *rr_data, int rr_len, v8::Local &r) { + if (rr_len == sizeof(struct in6_addr)) { + struct in6_addr a; + memcpy(&a, rr_data, sizeof(a)); + + char ip[INET6_ADDRSTRLEN]; + ip[0] = '\0'; + ::inet_ntop(AF_INET6, &a, ip, INET6_ADDRSTRLEN); + + v8::Local address = v8::String::New(ip); + r->Set(text_symbol, address); + return ARES_SUCCESS; + } + return ARES_EBADRESP; +} + +static int ParseRRData_MX(const unsigned char *abuf, int alen, const unsigned char *rr_data, int rr_len, v8::Local &r) { + int status; + + if (rr_len < 2) return ARES_EBADRESP; + + uint16_t priority; + memcpy(&priority, rr_data, sizeof(priority)); + priority = ntohs(priority); + rr_data += sizeof(priority); rr_len -= 2; + + char *hostname; + long len; + status = ares_expand_name(rr_data, abuf, alen, &hostname, &len); + if (status != ARES_SUCCESS) return status; + v8::Local v8_hostname = v8::String::New(hostname); + free(hostname); + if (len != rr_len) return ARES_EBADRESP; + + v8::Local v8_priority = v8::Integer::New(priority); + r->Set(priority_symbol, v8_priority); + r->Set(exchange_symbol, v8_hostname); + + v8::Local text = v8::String::Concat(v8_priority->ToString(), space_symbol); + text = v8::String::Concat(text, v8_hostname); + r->Set(text_symbol, text); + return ARES_SUCCESS; +} + +static int ParseRRData_NS(const unsigned char *abuf, int alen, const unsigned char *rr_data, int rr_len, v8::Local &r) { + int status; + + char *hostname; + long len; + status = ares_expand_name(rr_data, abuf, alen, &hostname, &len); + if (status != ARES_SUCCESS) return status; + v8::Local v8_hostname = v8::String::New(hostname); + free(hostname); + if (len != rr_len) return ARES_EBADRESP; + + r->Set(text_symbol, v8_hostname); + + return ARES_SUCCESS; +} + +static int ParseRRData_SOA(const unsigned char *abuf, int alen, const unsigned char *rr_data, int rr_len, v8::Local &r) { + int status; + long len; + + char *mname; /* primary NS */ + status = ares_expand_name(rr_data, abuf, alen, &mname, &len); + if (status != ARES_SUCCESS) return status; + v8::Local v8_mname = v8::String::New(mname); + free(mname); + if (len > rr_len) return ARES_EBADRESP; + rr_data += len; rr_len -= len; + + char *rname; /* owner mailbox */ + status = ares_expand_name(rr_data, abuf, alen, &rname, &len); + if (status != ARES_SUCCESS) return status; + v8::Local v8_rname = v8::String::New(rname); + free(rname); + if (len > rr_len) return ARES_EBADRESP; + rr_data += len; rr_len -= len; + + if (rr_len != 20) return ARES_EBADRESP; + + uint32_t serial, refresh, retry, expire, minimum; + memcpy(&serial, rr_data, sizeof(uint32_t)); serial = ntohl(serial); rr_data += sizeof(uint32_t); + memcpy(&refresh, rr_data, sizeof(uint32_t)); refresh = ntohl(refresh); rr_data += sizeof(uint32_t); + memcpy(&retry, rr_data, sizeof(uint32_t)); retry = ntohl(retry); rr_data += sizeof(uint32_t); + memcpy(&expire, rr_data, sizeof(uint32_t)); expire = ntohl(expire); rr_data += sizeof(uint32_t); + memcpy(&minimum, rr_data, sizeof(uint32_t)); minimum = ntohl(minimum); + + v8::Local + v8_serial = v8::Integer:: New(serial), + v8_refresh = v8::Integer:: New(refresh), + v8_retry = v8::Integer:: New(retry), + v8_expire = v8::Integer:: New(expire), + v8_minimum = v8::Integer:: New(minimum); + + v8::Local text = v8::String::Concat(v8_mname, space_symbol); + text = v8::String::Concat(text, v8_rname); text = v8::String::Concat(text, space_symbol); + text = v8::String::Concat(text, v8_serial->ToString()); text = v8::String::Concat(text, space_symbol); + text = v8::String::Concat(text, v8_refresh->ToString()); text = v8::String::Concat(text, space_symbol); + text = v8::String::Concat(text, v8_retry->ToString()); text = v8::String::Concat(text, space_symbol); + text = v8::String::Concat(text, v8_expire->ToString()); text = v8::String::Concat(text, space_symbol); + text = v8::String::Concat(text, v8_minimum->ToString()); + r->Set(text_symbol, text); + + r->Set(mname_symbol, v8_mname); + r->Set(rname_symbol, v8_rname); + r->Set(serial_symbol, v8_serial); + r->Set(refresh_symbol, v8_refresh); + r->Set(retry_symbol, v8_retry); + r->Set(expire_symbol, v8_expire); + r->Set(minimum_symbol, v8_minimum); + + return ARES_SUCCESS; +} + +static int ParseRRData_TXT(const unsigned char *, int , const unsigned char *rr_data, int rr_len, v8::Local &r) { + int status, i; + + if (0 == rr_len) return ARES_EBADRESP; + + v8::Local list = v8::Array::New(); + v8::Local s, text = v8::String::New(""), quote = v8::String::NewSymbol("\""); + for (i = 0; 0 < rr_len; i++) { + status = parse_char_string(rr_data, rr_len, s); + if (ARES_SUCCESS != status) return status; + + list->Set(v8::Integer::New(i), s); + + if (i != 0) text = v8::String::Concat(text, space_symbol); + text = v8::String::Concat(text, quote); + /* TODO: escaping */ + text = v8::String::Concat(text, s); + text = v8::String::Concat(text, quote); + } + + r->Set(data_symbol, list); + r->Set(text_symbol, text); + + return ARES_SUCCESS; +} + +static int ParseRRData_SRV(const unsigned char *abuf, int alen, const unsigned char *rr_data, int rr_len, v8::Local &r) { + int status; + + if (rr_len < 6) return ARES_EBADRESP; + + uint16_t priority, weight, port; + memcpy(&priority, rr_data, sizeof(uint16_t)); priority = ntohl(priority); rr_data += sizeof(uint16_t); rr_len -= sizeof(uint16_t); + memcpy(&weight, rr_data, sizeof(uint16_t)); weight = ntohl(weight); rr_data += sizeof(uint16_t); rr_len -= sizeof(uint16_t); + memcpy(&port, rr_data, sizeof(uint16_t)); port = ntohl(port); rr_data += sizeof(uint16_t); rr_len -= sizeof(uint16_t); + + char *target; + long len; + status = ares_expand_name(rr_data, abuf, alen, &target, &len); + if (status != ARES_SUCCESS) return status; + v8::Local v8_target = v8::String::New(target); + free(target); + if (len != rr_len) return ARES_EBADRESP; + + v8::Local + v8_priority = v8::Integer:: New(priority), + v8_weight = v8::Integer:: New(weight), + v8_port = v8::Integer:: New(port); + + r->Set(priority_symbol, v8_priority); + r->Set(weight_symbol, v8_weight); + r->Set(port_symbol, v8_port); + r->Set(target_symbol, v8_target); + + v8::Local text = v8::String::Concat(v8_priority->ToString(), space_symbol); + text = v8::String::Concat(text, v8_weight->ToString()); text = v8::String::Concat(text, space_symbol); + text = v8::String::Concat(text, v8_port->ToString()); text = v8::String::Concat(text, space_symbol); + text = v8::String::Concat(text, v8_target); + r->Set(text_symbol, text); + + return ARES_SUCCESS; +} + +/* indented ones are deprecated/experimental */ +static RR_TYPE rr_types[] = { + { "A", 1, ParseRRData_A, v8::Persistent() }, + { "NS", 2, ParseRRData_NS, v8::Persistent() }, + { "MD", 3, 0, v8::Persistent() }, + { "MF", 4, 0, v8::Persistent() }, + { "CNAME", 5, ParseRRData_NS, v8::Persistent() }, /* same RRDATA as NS */ + { "SOA", 6, ParseRRData_SOA, v8::Persistent() }, + { "MB", 7, 0, v8::Persistent() }, + { "MG", 8, 0, v8::Persistent() }, + { "MR", 9, 0, v8::Persistent() }, + { "NULL", 10, 0, v8::Persistent() }, + { "WKS", 11, 0, v8::Persistent() }, + { "PTR", 12, ParseRRData_NS, v8::Persistent() }, /* same RRDATA as NS */ + { "HINFO", 13, 0, v8::Persistent() }, + { "MINFO", 14, 0, v8::Persistent() }, + { "MX", 15, ParseRRData_MX, v8::Persistent() }, + { "TXT", 16, ParseRRData_TXT, v8::Persistent() }, + { "RP", 17, 0, v8::Persistent() }, + { "AFSDB", 18, 0, v8::Persistent() }, + { "SIG", 24, 0, v8::Persistent() }, + { "KEY", 25, 0, v8::Persistent() }, + { "AAAA", 28, ParseRRData_AAAA, v8::Persistent() }, + { "LOC", 29, 0, v8::Persistent() }, + { "SRV", 33, ParseRRData_SRV, v8::Persistent() }, + { "NAPTR", 35, 0, v8::Persistent() }, + { "KX", 36, 0, v8::Persistent() }, + { "CERT", 37, 0, v8::Persistent() }, + { "DNAME", 39, 0, v8::Persistent() }, + { "OPT", 41, 0, v8::Persistent() }, + { "APL", 42, 0, v8::Persistent() }, + { "DS", 43, 0, v8::Persistent() }, + { "SSHFP", 44, 0, v8::Persistent() }, + { "IPSECKEY", 45, 0, v8::Persistent() }, + { "RRSIG", 46, 0, v8::Persistent() }, + { "NSEC", 47, 0, v8::Persistent() }, + { "DNSKEY", 48, 0, v8::Persistent() }, + { "DHCID", 49, 0, v8::Persistent() }, + { "NSEC3", 50, 0, v8::Persistent() }, + { "NSEC3PARAM", 51, 0, v8::Persistent() }, + { "HIP", 55, 0, v8::Persistent() }, + { "SPF", 99, 0, v8::Persistent() }, + { "TKEY", 249, 0, v8::Persistent() }, + { "TSIG", 250, 0, v8::Persistent() }, + { "IXFR", 251, 0, v8::Persistent() }, + { "AXFR", 252, 0, v8::Persistent() }, + { "MAILB", 253, 0, v8::Persistent() }, + { "MAILA", 254, 0, v8::Persistent() }, + { "ANY", 255, 0, v8::Persistent() }, /* aka "*" */ + { "TA", 32768, 0, v8::Persistent() }, + { "DLV", 32769, 0, v8::Persistent() } +}; + +static RR_CLASS rr_classes[] = { + { "IN", 1, v8::Persistent() }, /* the Internet */ + { "CS", 2, v8::Persistent() }, /* the CSNET class (Obsolete - used only for examples in some obsolete RFCs) */ + { "CH", 3, v8::Persistent() }, /* the CHAOS class */ + { "HS", 4, v8::Persistent() }, /* Hesiod [Dyer 87] */ + { "ANY", 255, v8::Persistent() } /* same value as in rr_type, so no name conflict */ +}; + +void Initialize(v8::Handle target) { + v8::HandleScope scope; + + using namespace v8; + + int r = ares_library_init(ARES_LIB_INIT_ALL); + if (0 != r) { + // TODO + // ThrowException(Exception::Error(String::New(ares_strerror(r)))); + assert(r == 0); + } + + question_symbol = NODE_PSYMBOL("question"); + answer_symbol = NODE_PSYMBOL("answer"); + authority_symbol = NODE_PSYMBOL("authority"); + additional_symbol = NODE_PSYMBOL("additional"); + + type_symbol = NODE_PSYMBOL("type"); + ntype_symbol = NODE_PSYMBOL("ntype"); + class_symbol = NODE_PSYMBOL("class"); + nclass_symbol = NODE_PSYMBOL("nclass"); + ttl_symbol = NODE_PSYMBOL("ttl"); + rdata_symbol = NODE_PSYMBOL("rdata"); + text_symbol = NODE_PSYMBOL("text"); + data_symbol = NODE_PSYMBOL("data"); + + priority_symbol = NODE_PSYMBOL("priority"); + weight_symbol = NODE_PSYMBOL("weight"); + port_symbol = NODE_PSYMBOL("port"); + name_symbol = NODE_PSYMBOL("name"); + exchange_symbol = NODE_PSYMBOL("exchange"); + target_symbol = NODE_PSYMBOL("target"); + + mname_symbol = NODE_PSYMBOL("mname"); + rname_symbol = NODE_PSYMBOL("rname"); + serial_symbol = NODE_PSYMBOL("serial"); + refresh_symbol = NODE_PSYMBOL("refresh"); + retry_symbol = NODE_PSYMBOL("retry"); + expire_symbol = NODE_PSYMBOL("expire"); + minimum_symbol = NODE_PSYMBOL("minimum"); + + space_symbol = NODE_PSYMBOL(" ");; + + v8::Local types = v8::Object::New(); + for (unsigned int i = 0; i < sizeof(rr_types)/sizeof(rr_types[0]); i++) { + rr_types[i].symbol = NODE_PSYMBOL(rr_types[i].s); + target->Set(rr_types[i].symbol, v8::Integer::New(rr_types[i].n)); + } + for (unsigned int i = 0; i < sizeof(rr_classes)/sizeof(rr_classes[0]); i++) { + rr_classes[i].symbol = NODE_PSYMBOL(rr_classes[i].s); + target->Set(rr_classes[i].symbol, v8::Integer::New(rr_classes[i].n)); + } +} + +static inline const char *ares_errno_string(int errorno) { +#define ERRNO_CASE(e) case ARES_##e: return #e; + switch (errorno) { + 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) + default: + assert(0 && "Unhandled c-ares errno"); + return "(UNKNOWN)"; + } +} + +v8::Local AresException(int status) { + v8::Local code = v8::String::NewSymbol(ares_errno_string(status)); + v8::Local message = v8::String::NewSymbol(ares_strerror(status)); + + v8::Local cons1 = v8::String::Concat(code, v8::String::NewSymbol(", ")); + v8::Local cons2 = v8::String::Concat(cons1, message); + + v8::Local e = v8::Exception::Error(cons2); + + v8::Local obj = e->ToObject(); + obj->Set(v8::String::NewSymbol("errno"), v8::Integer::New(status)); + + return e; +} + +static void set_type(v8::Local &r, int rr_type) { + r->Set(ntype_symbol, v8::Integer::New(rr_type)); + for (unsigned int i = 0; i < sizeof(rr_types)/sizeof(rr_types[0]); i++) { + if (rr_type == rr_types[i].n) { + r->Set(type_symbol, rr_types[i].symbol); + break; + } + } +} +static void set_class(v8::Local &r, int rr_class) { + r->Set(nclass_symbol, v8::Integer::New(rr_class)); + for (unsigned int i = 0; i < sizeof(rr_classes)/sizeof(rr_classes[0]); i++) { + if (rr_class == rr_classes[i].n) { + r->Set(class_symbol, rr_classes[i].symbol); + break; + } + } +} + +static int parse_read_questions(const unsigned char *abuf, int alen, const unsigned char * &aptr, int count, v8::Local list) { + int status; + long len; + + /* skip each question */ + for (int i = 0; i < count; i++) { + int rr_type, rr_class; + char *rr_name; + v8::Local r = v8::Object::New(); + + /* Decode the RR up to the data field. */ + status = ares_expand_name(aptr, abuf, alen, &rr_name, &len); + if (status != ARES_SUCCESS) return status; + aptr += len; + if (aptr + QFIXEDSZ > abuf + alen) { + free(rr_name); + return ARES_EBADRESP; + } + + rr_type = DNS_RR_TYPE(aptr); + rr_class = DNS_RR_CLASS(aptr); + aptr += QFIXEDSZ; + + if (aptr > abuf + alen) { + free(rr_name); + return ARES_EBADRESP; + } + + r->Set(name_symbol, v8::String::New(rr_name)); + set_type(r, rr_type); + set_class(r, rr_class); + free(rr_name); + + list->Set(v8::Integer::New(i), r); + } + + return ARES_SUCCESS; +} + +static int parse_read_answers(const unsigned char *abuf, int alen, const unsigned char * &aptr, int count, v8::Local list) { + int status; + long len; + + /* Examine each resource record (RR) in turn. */ + for (int i = 0; i < count; i++) { + int rr_type, rr_class, rr_len, rr_ttl; + char *rr_name; + const unsigned char *rr_data; + v8::Local r = v8::Object::New(); + + /* Decode the RR up to the data field. */ + status = ares_expand_name(aptr, abuf, alen, &rr_name, &len); + if (status != ARES_SUCCESS) break; + aptr += len; + if (aptr + RRFIXEDSZ > abuf + alen) { + free(rr_name); + return ARES_EBADRESP; + } + + rr_type = DNS_RR_TYPE(aptr); + rr_class = DNS_RR_CLASS(aptr); + rr_ttl = DNS_RR_TTL(aptr); + rr_len = DNS_RR_LEN(aptr); + aptr += RRFIXEDSZ; + rr_data = aptr; + aptr += rr_len; + + if (aptr > abuf + alen) { + free(rr_name); + return ARES_EBADRESP; + } + + r->Set(name_symbol, v8::String::New(rr_name)); + set_class(r, rr_class); + r->Set(ttl_symbol, v8::Integer::New(rr_ttl)); + free(rr_name); + + r->Set(ntype_symbol, v8::Integer::New(rr_type)); + for (unsigned int t = 0; t < sizeof(rr_types)/sizeof(rr_types[0]); t++) { + if (rr_type == rr_types[t].n) { + r->Set(type_symbol, rr_types[t].symbol); + if (C_IN == rr_class && rr_types[t].parse_in) { + status = rr_types[t].parse_in(abuf, alen, rr_data, rr_len, r); + if (status != ARES_SUCCESS) return status; + } + break; + } + } + + r->Set(rdata_symbol, v8::String::New((const char*) rr_data, rr_len)); + + list->Set(v8::Integer::New(i), r); + } + + return ARES_SUCCESS; +} + +v8::Local parse_dns_response(const unsigned char *abuf, int alen, int *pstatus) { + unsigned int qdcount, ancount, nscount, arcount; + int status; + const unsigned char *aptr; + + if (pstatus) *pstatus = ARES_SUCCESS; + + v8::Local qd = v8::Array::New(), an = v8::Array::New(), ns = v8::Array::New(), ar = v8::Array::New(); + v8::Local msg = v8::Object::New(); + + /* Give up if abuf doesn't have room for a header. */ + if (alen < HFIXEDSZ) { + status = ARES_EBADRESP; + goto error; + } + + /* Fetch the question and answer count from the header. */ + qdcount = DNS_HEADER_QDCOUNT(abuf); + ancount = DNS_HEADER_ANCOUNT(abuf); + nscount = DNS_HEADER_NSCOUNT(abuf); + arcount = DNS_HEADER_ARCOUNT(abuf); + if (qdcount != 1) { + status = ARES_EBADRESP; + goto error; + } + + aptr = abuf + HFIXEDSZ; + + status = parse_read_questions(abuf, alen, aptr, qdcount, qd); + if (status != ARES_SUCCESS) goto error; + + status = parse_read_answers(abuf, alen, aptr, ancount, an); + if (status != ARES_SUCCESS) goto error; + + status = parse_read_answers(abuf, alen, aptr, nscount, ns); + if (status != ARES_SUCCESS) goto error; + + status = parse_read_answers(abuf, alen, aptr, arcount, ar); + if (status != ARES_SUCCESS) goto error; + + msg->Set(question_symbol, qd); + msg->Set(answer_symbol, an); + msg->Set(authority_symbol, ns); + msg->Set(additional_symbol, ar); + + return msg; + +error: + if (pstatus) *pstatus = status; + return AresException(status); +} + +} diff --git a/ares/dns_parser.h b/ares/dns_parser.h new file mode 100644 index 0000000..3fde855 --- /dev/null +++ b/ares/dns_parser.h @@ -0,0 +1,14 @@ +#ifndef DNS_PARSER_H_ +#define DNS_PARSER_H_ + +#include + +namespace dns_parser { + +void Initialize(v8::Handle target); + +v8::Local parse_dns_response(const unsigned char *abuf, int alen, int *status); +v8::Local AresException(int status); + +} +#endif // DNS_PARSER_H_ diff --git a/ares/wscript b/ares/wscript new file mode 100644 index 0000000..07ceecb --- /dev/null +++ b/ares/wscript @@ -0,0 +1,17 @@ +srcdir = '.' +blddir = 'build' +VERSION = '0.0.1' + +def set_options(opt): + opt.tool_options('compiler_cxx') + +def configure(conf): + conf.check_tool('compiler_cxx') + conf.check_tool('node_addon') + + conf.env['CXXFLAGS'] += [ '-O2', '-g', '-Wall', '-Wshadow', '-W' ] + +def build(bld): + obj = bld.new_task_gen('cxx', 'shlib', 'node_addon') + obj.target = 'ares' + obj.source = 'ares.cc dns_parser.cc' diff --git a/dns_native.js b/dns_native.js new file mode 100644 index 0000000..cc4a08b --- /dev/null +++ b/dns_native.js @@ -0,0 +1,123 @@ +var ares = require('./ares/build/default/ares'); + + +function rec_toString() { + var out = ""; + + console.log(this); + + out += ";; Question Section:\n"; + for (var i = 0, len = this.question.length; i < len; i++) { + var q = this.question[i]; + out += q.name + "\t" + (q.class || q.nclass) + "\t" + (q.type || q.ntype) + "\n"; + } + + out += ";; Answer Section:\n"; + for (var i = 0, len = this.answer.length; i < len; i++) { + var r = this.answer[i]; + out += r.name + "\t" + r.ttl + "\t" + (r.class || r.nclass) + "\t" + (r.type || r.ntype) + "\t" + (r.text || '') + "\n"; + } + out += ";; Authority Section:\n"; + for (var i = 0, len = this.authority.length; i < len; i++) { + var r = this.authority[i]; + out += r.name + "\t" + r.ttl + "\t" + (r.class || r.nclass) + "\t" + (r.type || r.ntype) + "\t" + (r.text || '') + "\n"; + } + out += ";; Additional Section:\n"; + for (var i = 0, len = this.additional.length; i < len; i++) { + var r = this.additional[i]; + out += r.name + "\t" + r.ttl + "\t" + (r.class || r.nclass) + "\t" + (r.type || r.ntype) + "\t" + (r.text || '') + "\n"; + } + + return out; +} + +function newChannel(servers) { + var channel; + var watchers = {}; + var activeWatchers = {}; + var timer = new process.Timer(); + + timer.callback = function () { + channel.processFD(ares.SOCKET_BAD, ares.SOCKET_BAD); + updateTimer(); + } + + function updateTimer() { + timer.stop(); + + // Were just checking to see if activeWatchers is empty or not + for (var socket in activeWatchers) { + if (activeWatchers.hasOwnProperty(socket)) { + var timeout = channel.timeout(-1); + + timer.start(timeout, 0); + // Short circuit the loop on first find. + return; + } + } + } + + channel = new ares.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 : ares.SOCKET_BAD, write ? socket : ares.SOCKET_BAD); + updateTimer(); + } + } + + watcher.stop(); + + if (!(read || write)) { + delete activeWatchers[socket]; + return; + } else { + watcher.set(socket, read == 1, write == 1); + watcher.start(); + activeWatchers[socket] = watcher; + } + + updateTimer(); + }}); + if (servers) { + channel.setServers(servers); + } + /* wrapper mapping type strings to numerical values */ + channel._query = channel.query; + channel.query = function(domain, type, callback) { + channel._query(domain, ares[type], function(err, result) { + if (result) result.toString = rec_toString; +// console.log(result); + callback(err, result); + }); + }; + + return channel; +}; + +var channel = newChannel(); + +exports.query = channel.query; + +exports.newChannel = newChannel; + +// ERROR CODES +exports.NODATA = ares.NODATA; +exports.FORMERR = ares.FORMERR; +exports.BADRESP = ares.BADRESP; +exports.NOTFOUND = ares.NOTFOUND; +exports.BADNAME = ares.BADNAME; +exports.TIMEOUT = ares.TIMEOUT; +exports.CONNREFUSED = ares.CONNREFUSED; +exports.NOMEM = ares.NOMEM; +exports.DESTRUCTION = ares.DESTRUCTION; + +exports.NOTIMP = ares.NOTIMP; +exports.EREFUSED = ares.EREFUSED; +exports.SERVFAIL = ares.SERVFAIL; diff --git a/main.js b/main.js new file mode 100644 index 0000000..d414b9c --- /dev/null +++ b/main.js @@ -0,0 +1,36 @@ + +var http = require('http'); +var dns = require('./dns_native'); +var url = require('url'); + +http.createServer(function (req, res) { + var parts, type, domain, ns; + + parts = url.parse(req.url).pathname.slice(1).split("/"); + + if (parts.length > 2) { + type = parts[0]; + domain = parts[1]; + ns = [ parts[2] ]; + } else if (parts.length > 1) { + type = parts[0]; + domain = parts[1]; + } else if (parts.length > 0) { + type = 'A'; + domain = parts[0]; + } else { + res.writeHead(500, {'Content-Type': 'text/plain'}); + res.end("Wrong request\n"); + return; + } + res.writeHead(200, {'Content-Type': 'text/plain'}); + + c = dns.newChannel(ns); + c.query(domain, type, function(err, response) { + if (err) { + res.end("Error resolving '" + type + "/" + domain + "': " + err.message + "\n"); + } else { + res.end(domain + ":\n" + response.toString() + "\n"); + } + }); +}).listen(4000);