// Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include #if defined(__OpenBSD__) || defined(__MINGW32__) # include #else # include #endif // Temporary hack: libuv should provide uv_inet_pton and uv_inet_ntop. #ifdef __MINGW32__ extern "C" { # include # include } # define uv_inet_pton ares_inet_pton # define uv_inet_ntop ares_inet_ntop #else // __POSIX__ # include # define uv_inet_pton inet_pton # define uv_inet_ntop inet_ntop #endif namespace node { namespace cares_wrap { using v8::Arguments; using v8::Array; using v8::Context; using v8::Function; using v8::Handle; using v8::HandleScope; using v8::Integer; using v8::Local; using v8::Null; using v8::Object; using v8::Persistent; using v8::String; using v8::Value; static Persistent onanswer_sym; static ares_channel ares_channel; static Local HostentToAddresses(struct hostent* host) { HandleScope scope; Local addresses = Array::New(); char ip[INET6_ADDRSTRLEN]; for (int i = 0; host->h_addr_list[i]; ++i) { uv_inet_ntop(host->h_addrtype, host->h_addr_list[i], ip, sizeof(ip)); Local address = String::New(ip); addresses->Set(Integer::New(i), address); } return scope.Close(addresses); } static Local HostentToNames(struct hostent* host) { HandleScope scope; Local names = Array::New(); for (int i = 0; host->h_aliases[i]; ++i) { Local address = String::New(host->h_aliases[i]); names->Set(Integer::New(i), address); } return scope.Close(names); } static const char* AresErrnoString(int errorno) { switch (errorno) { #define ERRNO_CASE(e) case ARES_##e: return #e; 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) #undef ERRNO_CASE default: assert(0 && "Unhandled c-ares error"); return "(UNKNOWN)"; } } static void SetAresErrno(int errorno) { HandleScope scope; Handle key = String::NewSymbol("errno"); Handle value = String::NewSymbol(AresErrnoString(errorno)); Context::GetCurrent()->Global()->Set(key, value); } class QueryWrap { public: QueryWrap() { HandleScope scope; object_ = Persistent::New(Object::New()); } ~QueryWrap() { assert(!object_.IsEmpty()); object_->DeleteHiddenValue(onanswer_sym); object_.Dispose(); object_.Clear(); } Handle GetObject() { return object_; } void SetOnAnswer(Handle onanswer) { assert(onanswer->IsFunction()); object_->SetHiddenValue(onanswer_sym, onanswer); } // Subclasses should implement the appropriate Send method. virtual int Send(const char* name) { assert(0); } virtual int Send(const char* name, int family) { assert(0); } protected: void* GetQueryArg() { return static_cast(this); } static void Callback(void *arg, int status, int timeouts, unsigned char* answer_buf, int answer_len) { QueryWrap* wrap = reinterpret_cast(arg); if (status != ARES_SUCCESS) { wrap->ParseError(status); } else { wrap->Parse(answer_buf, answer_len); } delete wrap; } static void Callback(void *arg, int status, int timeouts, struct hostent* host) { QueryWrap* wrap = reinterpret_cast(arg); if (status != ARES_SUCCESS) { wrap->ParseError(status); } else { wrap->Parse(host); } delete wrap; } Handle GetOnAnswer() { HandleScope scope; assert(!object_.IsEmpty()); Handle onanswer = object_->GetHiddenValue(onanswer_sym); assert(onanswer->IsFunction()); return scope.Close(Handle::Cast(onanswer)); } void CallOnAnswer(Local answer) { HandleScope scope; Local argv[2] = { Integer::New(0), answer }; GetOnAnswer()->Call(this->object_, 2, argv); } void CallOnAnswer(Local answer, Local family) { HandleScope scope; Local argv[3] = { Integer::New(0), answer, family }; GetOnAnswer()->Call(this->object_, 3, argv); } void ParseError(int status) { assert(status != ARES_SUCCESS); SetAresErrno(status); HandleScope scope; Local argv[1] = { Integer::New(-1) }; GetOnAnswer()->Call(this->object_, 1, argv); } // Subclasses should implement the appropriate Parse method. virtual void Parse(unsigned char* buf, int len) { assert(0); }; virtual void Parse(struct hostent* host) { assert(0); }; private: Persistent object_; }; class QueryAWrap: public QueryWrap { public: int Send(const char* name) { ares_query(ares_channel, name, ns_c_in, ns_t_a, Callback, GetQueryArg()); return 0; } protected: void Parse(unsigned char* buf, int len) { HandleScope scope; struct hostent* host; int status = ares_parse_a_reply(buf, len, &host, NULL, NULL); if (status != ARES_SUCCESS) { this->ParseError(status); return; } Local addresses = HostentToAddresses(host); ares_free_hostent(host); this->CallOnAnswer(addresses); } }; class QueryAaaaWrap: public QueryWrap { public: int Send(const char* name) { ares_query(ares_channel, name, ns_c_in, ns_t_aaaa, Callback, GetQueryArg()); return 0; } protected: void Parse(unsigned char* buf, int len) { HandleScope scope; struct hostent* host; int status = ares_parse_aaaa_reply(buf, len, &host, NULL, NULL); if (status != ARES_SUCCESS) { this->ParseError(status); return; } Local addresses = HostentToAddresses(host); ares_free_hostent(host); this->CallOnAnswer(addresses); } }; class QueryCnameWrap: public QueryWrap { public: int Send(const char* name) { ares_query(ares_channel, name, ns_c_in, ns_t_cname, Callback, GetQueryArg()); return 0; } protected: void Parse(unsigned char* buf, int len) { HandleScope scope; struct hostent* host; int status = ares_parse_a_reply(buf, len, &host, NULL, NULL); if (status != ARES_SUCCESS) { this->ParseError(status); return; } // A cname lookup always returns a single record but we follow the // common API here. Local result = Array::New(1); result->Set(0, String::New(host->h_name)); ares_free_hostent(host); this->CallOnAnswer(result); } }; class QueryMxWrap: public QueryWrap { public: int Send(const char* name) { ares_query(ares_channel, name, ns_c_in, ns_t_mx, Callback, GetQueryArg()); return 0; } protected: void Parse(unsigned char* buf, int len) { HandleScope scope; struct ares_mx_reply* mx_start; int status = ares_parse_mx_reply(buf, len, &mx_start); if (status != ARES_SUCCESS) { this->ParseError(status); return; } Local mx_records = Array::New(); Local exchange_symbol = String::NewSymbol("exchange"); Local priority_symbol = String::NewSymbol("priority"); int i = 0; for (struct ares_mx_reply* mx_current = mx_start; mx_current; mx_current = mx_current->next) { Local mx_record = Object::New(); mx_record->Set(exchange_symbol, String::New(mx_current->host)); mx_record->Set(priority_symbol, Integer::New(mx_current->priority)); mx_records->Set(Integer::New(i++), mx_record); } ares_free_data(mx_start); this->CallOnAnswer(mx_records); } }; class QueryNsWrap: public QueryWrap { public: int Send(const char* name) { ares_query(ares_channel, name, ns_c_in, ns_t_ns, Callback, GetQueryArg()); return 0; } protected: void Parse(unsigned char* buf, int len) { struct hostent* host; int status = ares_parse_ns_reply(buf, len, &host); if (status != ARES_SUCCESS) { this->ParseError(status); return; } Local names = HostentToNames(host); ares_free_hostent(host); this->CallOnAnswer(names); } }; class QuerySrvWrap: public QueryWrap { public: int Send(const char* name) { ares_query(ares_channel, name, ns_c_in, ns_t_srv, Callback, GetQueryArg()); return 0; } protected: void Parse(unsigned char* buf, int len) { HandleScope scope; struct ares_srv_reply* srv_start; int status = ares_parse_srv_reply(buf, len, &srv_start); if (status != ARES_SUCCESS) { this->ParseError(status); return; } Local srv_records = Array::New(); Local name_symbol = String::NewSymbol("name"); Local port_symbol = String::NewSymbol("port"); Local priority_symbol = String::NewSymbol("priority"); Local weight_symbol = String::NewSymbol("weight"); int i = 0; for (struct ares_srv_reply* srv_current = srv_start; srv_current; srv_current = srv_current->next) { Local srv_record = Object::New(); srv_record->Set(name_symbol, String::New(srv_current->host)); srv_record->Set(port_symbol, Integer::New(srv_current->port)); srv_record->Set(priority_symbol, Integer::New(srv_current->priority)); srv_record->Set(weight_symbol, Integer::New(srv_current->weight)); srv_records->Set(Integer::New(i++), srv_record); } ares_free_data(srv_start); this->CallOnAnswer(srv_records); } }; class GetHostByAddrWrap: public QueryWrap { public: int Send(const char* name) { int length, family; char address_buffer[sizeof(struct in6_addr)]; if (uv_inet_pton(AF_INET, name, &address_buffer) == 1) { length = sizeof(struct in_addr); family = AF_INET; } else if (uv_inet_pton(AF_INET6, name, &address_buffer) == 1) { length = sizeof(struct in6_addr); family = AF_INET6; } else { return ARES_ENOTIMP; } ares_gethostbyaddr(ares_channel, address_buffer, length, family, Callback, GetQueryArg()); return 0; } protected: void Parse(struct hostent* host) { HandleScope scope; this->CallOnAnswer(HostentToNames(host)); } }; class GetHostByNameWrap: public QueryWrap { public: int Send(const char* name, int family) { ares_gethostbyname(ares_channel, name, family, Callback, GetQueryArg()); return 0; } protected: void Parse(struct hostent* host) { HandleScope scope; Local addresses = HostentToAddresses(host); Local family = Integer::New(host->h_addrtype); this->CallOnAnswer(addresses, family); } }; template static Handle Query(const Arguments& args) { HandleScope scope; assert(!args.IsConstructCall()); assert(args.Length() >= 2); assert(args[1]->IsFunction()); Wrap* wrap = new Wrap(); wrap->SetOnAnswer(args[1]); // We must cache the wrap's js object here, because cares might make the // callback from the wrap->Send stack. This will destroy the wrap's internal // object reference, causing wrap->GetObject() to return undefined. Local object = Local::New(wrap->GetObject()); String::Utf8Value name(args[0]->ToString()); int r = wrap->Send(*name); if (r) { SetAresErrno(r); delete wrap; return scope.Close(v8::Null()); } else { return scope.Close(object); } } template static Handle QueryWithFamily(const Arguments& args) { HandleScope scope; assert(!args.IsConstructCall()); assert(args.Length() >= 3); assert(args[2]->IsFunction()); Wrap* wrap = new Wrap(); wrap->SetOnAnswer(args[2]); // We must cache the wrap's js object here, because cares might make the // callback from the wrap->Send stack. This will destroy the wrap's internal // object reference, causing wrap->GetObject() to return undefined. Local object = Local::New(wrap->GetObject()); String::Utf8Value name(args[0]->ToString()); int family = args[1]->Int32Value(); int r = wrap->Send(*name, family); if (r) { SetAresErrno(r); delete wrap; return scope.Close(v8::Null()); } else { return scope.Close(object); } } static void Initialize(Handle target) { HandleScope scope; int r; r = ares_library_init(ARES_LIB_INIT_ALL); assert(r == ARES_SUCCESS); struct ares_options options; uv_ares_init_options(&ares_channel, &options, 0); assert(r == 0); NODE_SET_METHOD(target, "queryA", Query); NODE_SET_METHOD(target, "queryAaaa", Query); NODE_SET_METHOD(target, "queryCname", Query); NODE_SET_METHOD(target, "queryMx", Query); NODE_SET_METHOD(target, "queryNs", Query); NODE_SET_METHOD(target, "querySrv", Query); NODE_SET_METHOD(target, "getHostByAddr", Query); NODE_SET_METHOD(target, "getHostByName", QueryWithFamily); target->Set(String::NewSymbol("AF_INET"), Integer::New(AF_INET)); target->Set(String::NewSymbol("AF_INET6"), Integer::New(AF_INET6)); target->Set(String::NewSymbol("AF_UNSPEC"), Integer::New(AF_UNSPEC)); onanswer_sym = Persistent::New(String::NewSymbol("onanswer")); } } // namespace cares_wrap } // namespace node NODE_MODULE(node_cares_wrap, node::cares_wrap::Initialize);