// 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 "cares_wrap.h" #include "ada.h" #include "async_wrap-inl.h" #include "base_object-inl.h" #include "env-inl.h" #include "memory_tracker-inl.h" #include "nbytes.h" #include "node.h" #include "node_errors.h" #include "node_external_reference.h" #include "req_wrap-inl.h" #include "util-inl.h" #include "uv.h" #include "v8.h" #include #include #include #include #include #ifndef T_TLSA #define T_TLSA 52 /* TLSA certificate association */ #endif #ifndef T_CAA # define T_CAA 257 /* Certification Authority Authorization */ #endif // OpenBSD does not define these #ifndef AI_ALL # define AI_ALL 0 #endif #ifndef AI_V4MAPPED # define AI_V4MAPPED 0 #endif namespace node { namespace cares_wrap { using v8::Array; using v8::ArrayBuffer; using v8::Context; using v8::DictionaryTemplate; using v8::EscapableHandleScope; using v8::Exception; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::HandleScope; using v8::Int32; using v8::Integer; using v8::Isolate; using v8::Just; using v8::JustVoid; using v8::Local; using v8::LocalVector; using v8::Maybe; using v8::MaybeLocal; using v8::Nothing; using v8::Null; using v8::Object; using v8::String; using v8::Uint32; using v8::Undefined; using v8::Value; namespace { Mutex ares_library_mutex; inline uint16_t cares_get_16bit(const unsigned char* p) { return static_cast(p[0] << 8U) | (static_cast(p[1])); } void ares_poll_cb(uv_poll_t* watcher, int status, int events) { NodeAresTask* task = ContainerOf(&NodeAresTask::poll_watcher, watcher); ChannelWrap* channel = task->channel; /* Reset the idle timer */ uv_timer_again(channel->timer_handle()); if (status < 0) { /* An error happened. Just pretend that the socket is both readable and */ /* writable. */ ares_process_fd(channel->cares_channel(), task->sock, task->sock); return; } /* Process DNS responses */ ares_process_fd(channel->cares_channel(), events & UV_READABLE ? task->sock : ARES_SOCKET_BAD, events & UV_WRITABLE ? task->sock : ARES_SOCKET_BAD); } void ares_poll_close_cb(uv_poll_t* watcher) { std::unique_ptr free_me( ContainerOf(&NodeAresTask::poll_watcher, watcher)); } /* Callback from ares when socket operation is started */ void ares_sockstate_cb(void* data, ares_socket_t sock, int read, int write) { ChannelWrap* channel = static_cast(data); NodeAresTask* task; NodeAresTask lookup_task; lookup_task.sock = sock; auto it = channel->task_list()->find(&lookup_task); task = (it == channel->task_list()->end()) ? nullptr : *it; if (read || write) { if (!task) { /* New socket */ channel->StartTimer(); task = NodeAresTask::Create(channel, sock); if (task == nullptr) { /* This should never happen unless we're out of memory or something */ /* is seriously wrong. The socket won't be polled, but the query will */ /* eventually time out. */ return; } channel->task_list()->insert(task); } /* This should never fail. If it fails anyway, the query will eventually */ /* time out. */ uv_poll_start(&task->poll_watcher, (read ? UV_READABLE : 0) | (write ? UV_WRITABLE : 0), ares_poll_cb); } else { if (task != nullptr) { channel->task_list()->erase(it); channel->env()->CloseHandle(&task->poll_watcher, ares_poll_close_cb); } if (channel->task_list()->empty()) { channel->CloseTimer(); } } } MaybeLocal HostentToNames(Environment* env, struct hostent* host) { EscapableHandleScope scope(env->isolate()); LocalVector names(env->isolate()); for (uint32_t i = 0; host->h_aliases[i] != nullptr; ++i) { names.emplace_back(OneByteString(env->isolate(), host->h_aliases[i])); } return scope.Escape(Array::New(env->isolate(), names.data(), names.size())); } MaybeLocal HostentToNames(Environment* env, struct hostent* host, Local names) { size_t offset = names->Length(); for (uint32_t i = 0; host->h_aliases[i] != nullptr; ++i) { if (names ->Set(env->context(), i + offset, OneByteString(env->isolate(), host->h_aliases[i])) .IsNothing()) { return {}; } } return names; } template Local AddrTTLToArray( Environment* env, const T* addrttls, size_t naddrttls) { MaybeStackBuffer, 8> ttls(naddrttls); for (size_t i = 0; i < naddrttls; i++) { ttls[i] = Integer::NewFromUnsigned(env->isolate(), addrttls[i].ttl); } return Array::New(env->isolate(), ttls.out(), naddrttls); } Maybe ParseGeneralReply(Environment* env, const unsigned char* buf, int len, int* type, Local ret, void* addrttls = nullptr, int* naddrttls = nullptr) { HandleScope handle_scope(env->isolate()); hostent* host; int status; switch (*type) { case ns_t_a: case ns_t_cname: case ns_t_cname_or_a: status = ares_parse_a_reply(buf, len, &host, static_cast(addrttls), naddrttls); break; case ns_t_aaaa: status = ares_parse_aaaa_reply(buf, len, &host, static_cast(addrttls), naddrttls); break; case ns_t_ns: status = ares_parse_ns_reply(buf, len, &host); break; case ns_t_ptr: status = ares_parse_ptr_reply(buf, len, nullptr, 0, AF_INET, &host); break; default: UNREACHABLE("Bad NS type"); } if (status != ARES_SUCCESS) return Just(status); CHECK_NOT_NULL(host); HostEntPointer ptr(host); /* If it's `CNAME`, return the CNAME value; * And if it's `CNAME_OR_A` and it has value in `h_name` and `h_aliases[0]`, * we consider it's a CNAME record, otherwise we consider it's an A record. */ if ((*type == ns_t_cname_or_a && ptr->h_name && ptr->h_aliases[0]) || *type == ns_t_cname) { // A cname lookup always returns a single record but we follow the // common API here. *type = ns_t_cname; if (ret->Set(env->context(), ret->Length(), OneByteString(env->isolate(), ptr->h_name)) .IsNothing()) { return Nothing(); } return Just(ARES_SUCCESS); } if (*type == ns_t_cname_or_a) *type = ns_t_a; if (*type == ns_t_ns) { if (HostentToNames(env, ptr.get(), ret).IsEmpty()) { return Nothing(); } } else if (*type == ns_t_ptr) { uint32_t offset = ret->Length(); for (uint32_t i = 0; ptr->h_aliases[i] != nullptr; i++) { auto alias = OneByteString(env->isolate(), ptr->h_aliases[i]); if (ret->Set(env->context(), i + offset, alias).IsNothing()) { return Nothing(); } } } else { uint32_t offset = ret->Length(); char ip[INET6_ADDRSTRLEN]; for (uint32_t i = 0; ptr->h_addr_list[i] != nullptr; ++i) { uv_inet_ntop(ptr->h_addrtype, ptr->h_addr_list[i], ip, sizeof(ip)); auto address = OneByteString(env->isolate(), ip); if (ret->Set(env->context(), i + offset, address).IsNothing()) { return Nothing(); } } } return Just(ARES_SUCCESS); } Maybe ParseMxReply(Environment* env, const unsigned char* buf, int len, Local ret, bool need_type = false) { HandleScope handle_scope(env->isolate()); auto tmpl = env->mx_record_template(); if (tmpl.IsEmpty()) { static constexpr std::string_view names[] = { "exchange", "priority", "type", }; tmpl = DictionaryTemplate::New(env->isolate(), names); env->set_mx_record_template(tmpl); } struct ares_mx_reply* mx_start; int status = ares_parse_mx_reply(buf, len, &mx_start); if (status != ARES_SUCCESS) return Just(status); DeleteFnPtr free_me(mx_start); uint32_t offset = ret->Length(); ares_mx_reply* current = mx_start; MaybeLocal values[] = { Undefined(env->isolate()), // exchange Undefined(env->isolate()), // priority Undefined(env->isolate()), // type }; for (uint32_t i = 0; current != nullptr; ++i, current = current->next) { values[0] = OneByteString(env->isolate(), current->host); values[1] = Integer::New(env->isolate(), current->priority); values[2] = env->dns_mx_string(); Local record; if (!NewDictionaryInstance(env->context(), tmpl, values).ToLocal(&record) || ret->Set(env->context(), i + offset, record).IsNothing()) { return Nothing(); } } return Just(ARES_SUCCESS); } Maybe ParseCaaReply(Environment* env, const unsigned char* buf, int len, Local ret, bool need_type = false) { HandleScope handle_scope(env->isolate()); auto tmpl = env->caa_record_template(); if (tmpl.IsEmpty()) { static constexpr std::string_view names[] = { "critical", "type", }; tmpl = DictionaryTemplate::New(env->isolate(), names); env->set_caa_record_template(tmpl); } struct ares_caa_reply* caa_start; int status = ares_parse_caa_reply(buf, len, &caa_start); if (status != ARES_SUCCESS) return Just(status); DeleteFnPtr free_me(caa_start); MaybeLocal values[] = { Undefined(env->isolate()), // critical Undefined(env->isolate()), // type }; uint32_t offset = ret->Length(); ares_caa_reply* current = caa_start; for (uint32_t i = 0; current != nullptr; ++i, current = current->next) { values[0] = Integer::New(env->isolate(), current->critical); values[1] = env->dns_caa_string(); Local caa_record; if (!NewDictionaryInstance(env->context(), tmpl, values) .ToLocal(&caa_record)) { return Nothing(); } // This additional property is not part of the template as it is // variable based on the record. if (caa_record ->Set(env->context(), OneByteString(env->isolate(), current->property), OneByteString(env->isolate(), current->value)) .IsNothing()) { return Nothing(); } if (ret->Set(env->context(), i + offset, caa_record).IsNothing()) { return Nothing(); } } return Just(ARES_SUCCESS); } Maybe ParseTlsaReply(Environment* env, unsigned char* buf, int len, Local ret) { EscapableHandleScope handle_scope(env->isolate()); auto tmpl = env->tlsa_record_template(); if (tmpl.IsEmpty()) { static constexpr std::string_view names[] = { "certUsage", "selector", "match", "data", }; tmpl = DictionaryTemplate::New(env->isolate(), names); env->set_tlsa_record_template(tmpl); } ares_dns_record_t* dnsrec = nullptr; int status = ares_dns_parse(buf, len, 0, &dnsrec); if (status != ARES_SUCCESS) { ares_dns_record_destroy(dnsrec); return Just(status); } DeleteFnPtr free_me(dnsrec); uint32_t offset = ret->Length(); size_t rr_count = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); MaybeLocal values[] = { Undefined(env->isolate()), // certUsage Undefined(env->isolate()), // selector Undefined(env->isolate()), // match Undefined(env->isolate()), // data }; for (size_t i = 0; i < rr_count; i++) { const ares_dns_rr_t* rr = ares_dns_record_rr_get(dnsrec, ARES_SECTION_ANSWER, i); if (ares_dns_rr_get_type(rr) != ARES_REC_TYPE_TLSA) continue; unsigned char certusage = ares_dns_rr_get_u8(rr, ARES_RR_TLSA_CERT_USAGE); unsigned char selector = ares_dns_rr_get_u8(rr, ARES_RR_TLSA_SELECTOR); unsigned char match = ares_dns_rr_get_u8(rr, ARES_RR_TLSA_MATCH); size_t data_len; const unsigned char* data = ares_dns_rr_get_bin(rr, ARES_RR_TLSA_DATA, &data_len); if (!data || data_len == 0) continue; Local data_ab = ArrayBuffer::New(env->isolate(), data_len); memcpy(data_ab->Data(), data, data_len); values[0] = Integer::NewFromUnsigned(env->isolate(), certusage); values[1] = Integer::NewFromUnsigned(env->isolate(), selector); values[2] = Integer::NewFromUnsigned(env->isolate(), match); values[3] = data_ab; Local tlsa_rec; if (!NewDictionaryInstance(env->context(), tmpl, values) .ToLocal(&tlsa_rec) || ret->Set(env->context(), offset + i, tlsa_rec).IsNothing()) { return Nothing(); } } return Just(ARES_SUCCESS); } Maybe ParseTxtReply(Environment* env, const unsigned char* buf, int len, Local ret, bool need_type = false) { HandleScope handle_scope(env->isolate()); auto tmpl = env->txt_record_template(); if (tmpl.IsEmpty()) { static constexpr std::string_view names[] = { "entries", "type", }; tmpl = DictionaryTemplate::New(env->isolate(), names); env->set_txt_record_template(tmpl); } struct ares_txt_ext* txt_out; int status = ares_parse_txt_reply_ext(buf, len, &txt_out); if (status != ARES_SUCCESS) return Just(status); DeleteFnPtr free_me(txt_out); Local txt_chunk; LocalVector chunks(env->isolate()); struct ares_txt_ext* current = txt_out; uint32_t i = 0; uint32_t offset = ret->Length(); MaybeLocal values[] = { Undefined(env->isolate()), // entries Undefined(env->isolate()), // type }; for (; current != nullptr; current = current->next) { Local txt = OneByteString(env->isolate(), current->txt, current->length); // New record found - write out the current chunk if (current->record_start) { if (!chunks.empty()) { auto txt_chunk = Array::New(env->isolate(), chunks.data(), chunks.size()); chunks.clear(); if (need_type) { values[0] = txt_chunk; values[1] = env->dns_txt_string(); Local elem; if (!NewDictionaryInstance(env->context(), tmpl, values) .ToLocal(&elem) || ret->Set(env->context(), offset + i++, elem).IsNothing()) { return Nothing(); } } else if (ret->Set(env->context(), offset + i++, txt_chunk) .IsNothing()) { return Nothing(); } } txt_chunk = Array::New(env->isolate()); } chunks.push_back(txt); } // Push last chunk if it isn't empty if (!chunks.empty()) { txt_chunk = Array::New(env->isolate(), chunks.data(), chunks.size()); if (need_type) { values[0] = txt_chunk; values[1] = env->dns_txt_string(); Local elem; if (!NewDictionaryInstance(env->context(), tmpl, values).ToLocal(&elem) || ret->Set(env->context(), offset + i, elem).IsNothing()) { return Nothing(); } } else if (ret->Set(env->context(), offset + i, txt_chunk).IsNothing()) { return Nothing(); } } return Just(ARES_SUCCESS); } Maybe ParseSrvReply(Environment* env, const unsigned char* buf, int len, Local ret, bool need_type = false) { HandleScope handle_scope(env->isolate()); auto tmpl = env->srv_record_template(); if (tmpl.IsEmpty()) { static constexpr std::string_view names[] = { "name", "port", "priority", "weight", "type", }; tmpl = DictionaryTemplate::New(env->isolate(), names); env->set_srv_record_template(tmpl); } struct ares_srv_reply* srv_start; int status = ares_parse_srv_reply(buf, len, &srv_start); if (status != ARES_SUCCESS) return Just(status); DeleteFnPtr free_me(srv_start); MaybeLocal values[] = { Undefined(env->isolate()), // name Undefined(env->isolate()), // port Undefined(env->isolate()), // priority Undefined(env->isolate()), // weight Undefined(env->isolate()), // type }; ares_srv_reply* current = srv_start; int offset = ret->Length(); for (uint32_t i = 0; current != nullptr; ++i, current = current->next) { values[0] = OneByteString(env->isolate(), current->host); values[1] = Integer::New(env->isolate(), current->port); values[2] = Integer::New(env->isolate(), current->priority); values[3] = Integer::New(env->isolate(), current->weight); values[4] = env->dns_srv_string(); Local srv_record; if (!NewDictionaryInstance(env->context(), tmpl, values) .ToLocal(&srv_record) || ret->Set(env->context(), i + offset, srv_record).IsNothing()) { return Nothing(); } } return Just(ARES_SUCCESS); } Maybe ParseNaptrReply(Environment* env, const unsigned char* buf, int len, Local ret, bool need_type = false) { HandleScope handle_scope(env->isolate()); auto tmpl = env->naptr_record_template(); if (tmpl.IsEmpty()) { static constexpr std::string_view names[] = { "flags", "service", "regexp", "replacement", "order", "preference", "type", }; tmpl = DictionaryTemplate::New(env->isolate(), names); env->set_naptr_record_template(tmpl); } ares_naptr_reply* naptr_start; int status = ares_parse_naptr_reply(buf, len, &naptr_start); if (status != ARES_SUCCESS) return Just(status); DeleteFnPtr free_me(naptr_start); MaybeLocal values[] = { Undefined(env->isolate()), // flags Undefined(env->isolate()), // service Undefined(env->isolate()), // regexp Undefined(env->isolate()), // replacement Undefined(env->isolate()), // order Undefined(env->isolate()), // preference Undefined(env->isolate()), // type }; ares_naptr_reply* current = naptr_start; int offset = ret->Length(); for (uint32_t i = 0; current != nullptr; ++i, current = current->next) { values[0] = OneByteString(env->isolate(), current->flags); values[1] = OneByteString(env->isolate(), current->service); values[2] = OneByteString(env->isolate(), current->regexp); values[3] = OneByteString(env->isolate(), current->replacement); values[4] = Integer::New(env->isolate(), current->order); values[5] = Integer::New(env->isolate(), current->preference); if (need_type) { values[6] = env->dns_naptr_string(); } Local naptr_record; if (!NewDictionaryInstance(env->context(), tmpl, values) .ToLocal(&naptr_record) || ret->Set(env->context(), i + offset, naptr_record).IsNothing()) { return Nothing(); } } return Just(ARES_SUCCESS); } Local getSoaRecordTemplate(Environment* env) { auto tmpl = env->soa_record_template(); if (tmpl.IsEmpty()) { static constexpr std::string_view names[] = { "nsname", "hostmaster", "serial", "refresh", "retry", "expire", "minttl", "type", }; tmpl = DictionaryTemplate::New(env->isolate(), names); env->set_soa_record_template(tmpl); } return tmpl; } Maybe ParseSoaReply(Environment* env, unsigned char* buf, int len, Local* ret) { EscapableHandleScope handle_scope(env->isolate()); auto tmpl = getSoaRecordTemplate(env); // Manage memory using standardard smart pointer std::unique_tr struct AresDeleter { void operator()(char* ptr) const noexcept { ares_free_string(ptr); } }; using ares_unique_ptr = std::unique_ptr; // Can't use ares_parse_soa_reply() here which can only parse single record const unsigned int ancount = cares_get_16bit(buf + 6); unsigned char* ptr = buf + NS_HFIXEDSZ; char* name_temp = nullptr; long temp_len; // NOLINT(runtime/int) int status = ares_expand_name(ptr, buf, len, &name_temp, &temp_len); if (status != ARES_SUCCESS) { // returns EBADRESP in case of invalid input return Just(status == ARES_EBADNAME ? ARES_EBADRESP : status); } const ares_unique_ptr name(name_temp); if (ptr + temp_len + NS_QFIXEDSZ > buf + len) { return Just(ARES_EBADRESP); } ptr += temp_len + NS_QFIXEDSZ; MaybeLocal values[] = { Undefined(env->isolate()), // nsname Undefined(env->isolate()), // hostmaster Undefined(env->isolate()), // serial Undefined(env->isolate()), // refresh Undefined(env->isolate()), // retry Undefined(env->isolate()), // expire Undefined(env->isolate()), // minttl Undefined(env->isolate()), // type }; for (unsigned int i = 0; i < ancount; i++) { char* rr_name_temp = nullptr; long rr_temp_len; // NOLINT(runtime/int) int status2 = ares_expand_name(ptr, buf, len, &rr_name_temp, &rr_temp_len); if (status2 != ARES_SUCCESS) return Just(status2 == ARES_EBADNAME ? ARES_EBADRESP : status2); const ares_unique_ptr rr_name(rr_name_temp); ptr += rr_temp_len; if (ptr + NS_RRFIXEDSZ > buf + len) { return Just(ARES_EBADRESP); } const int rr_type = cares_get_16bit(ptr); const int rr_len = cares_get_16bit(ptr + 8); ptr += NS_RRFIXEDSZ; // only need SOA if (rr_type == ns_t_soa) { char* nsname_temp = nullptr; long nsname_temp_len; // NOLINT(runtime/int) int status3 = ares_expand_name(ptr, buf, len, &nsname_temp, &nsname_temp_len); if (status3 != ARES_SUCCESS) { return Just(status3 == ARES_EBADNAME ? ARES_EBADRESP : status3); } const ares_unique_ptr nsname(nsname_temp); ptr += nsname_temp_len; char* hostmaster_temp = nullptr; long hostmaster_temp_len; // NOLINT(runtime/int) int status4 = ares_expand_name(ptr, buf, len, &hostmaster_temp, &hostmaster_temp_len); if (status4 != ARES_SUCCESS) { return Just(status4 == ARES_EBADNAME ? ARES_EBADRESP : status4); } const ares_unique_ptr hostmaster(hostmaster_temp); ptr += hostmaster_temp_len; if (ptr + 5 * 4 > buf + len) { return Just(ARES_EBADRESP); } const unsigned int serial = nbytes::ReadUint32BE(ptr + 0 * 4); const unsigned int refresh = nbytes::ReadUint32BE(ptr + 1 * 4); const unsigned int retry = nbytes::ReadUint32BE(ptr + 2 * 4); const unsigned int expire = nbytes::ReadUint32BE(ptr + 3 * 4); const unsigned int minttl = nbytes::ReadUint32BE(ptr + 4 * 4); values[0] = OneByteString(env->isolate(), nsname.get()); values[1] = OneByteString(env->isolate(), hostmaster.get()); values[2] = Integer::NewFromUnsigned(env->isolate(), serial); values[3] = Integer::New(env->isolate(), refresh); values[4] = Integer::New(env->isolate(), retry); values[5] = Integer::New(env->isolate(), expire); values[6] = Integer::NewFromUnsigned(env->isolate(), minttl); values[7] = env->dns_soa_string(); Local soa_record; if (!NewDictionaryInstance(env->context(), tmpl, values) .ToLocal(&soa_record)) { return Nothing(); } *ret = handle_scope.Escape(soa_record); break; } ptr += rr_len; } return Just(ARES_SUCCESS); } } // anonymous namespace ChannelWrap::ChannelWrap(Environment* env, Local object, int timeout, int tries, int max_timeout) : AsyncWrap(env, object, PROVIDER_DNSCHANNEL), timeout_(timeout), tries_(tries), max_timeout_(max_timeout) { MakeWeak(); Setup(); } void ChannelWrap::MemoryInfo(MemoryTracker* tracker) const { if (timer_handle_ != nullptr) tracker->TrackField("timer_handle", *timer_handle_); tracker->TrackField("task_list", task_list_, "NodeAresTask::List"); } void ChannelWrap::New(const FunctionCallbackInfo& args) { CHECK(args.IsConstructCall()); CHECK_EQ(args.Length(), 3); CHECK(args[0]->IsInt32()); CHECK(args[1]->IsInt32()); CHECK(args[2]->IsInt32()); const int timeout = args[0].As()->Value(); const int tries = args[1].As()->Value(); const int max_timeout = args[2].As()->Value(); Environment* env = Environment::GetCurrent(args); new ChannelWrap(env, args.This(), timeout, tries, max_timeout); } GetAddrInfoReqWrap::GetAddrInfoReqWrap(Environment* env, Local req_wrap_obj, uint8_t order) : ReqWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_GETADDRINFOREQWRAP), order_(order) {} GetNameInfoReqWrap::GetNameInfoReqWrap( Environment* env, Local req_wrap_obj) : ReqWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_GETNAMEINFOREQWRAP) {} /* This is called once per second by loop->timer. It is used to constantly */ /* call back into c-ares for possibly processing timeouts. */ void ChannelWrap::AresTimeout(uv_timer_t* handle) { ChannelWrap* channel = static_cast(handle->data); CHECK_EQ(channel->timer_handle(), handle); ares_process_fd(channel->cares_channel(), ARES_SOCKET_BAD, ARES_SOCKET_BAD); } void NodeAresTask::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("channel", channel); } /* Allocates and returns a new NodeAresTask */ NodeAresTask* NodeAresTask::Create(ChannelWrap* channel, ares_socket_t sock) { auto task = new NodeAresTask(); task->channel = channel; task->sock = sock; if (uv_poll_init_socket(channel->env()->event_loop(), &task->poll_watcher, sock) < 0) { /* This should never happen. */ delete task; return nullptr; } return task; } void ChannelWrap::Setup() { struct ares_options options; memset(&options, 0, sizeof(options)); options.flags = ARES_FLAG_NOCHECKRESP; options.sock_state_cb = ares_sockstate_cb; options.sock_state_cb_data = this; options.timeout = timeout_; options.tries = tries_; options.qcache_max_ttl = 0; int r; if (!library_inited_) { Mutex::ScopedLock lock(ares_library_mutex); // Multiple calls to ares_library_init() increase a reference counter, // so this is a no-op except for the first call to it. r = ares_library_init(ARES_LIB_INIT_ALL); if (r != ARES_SUCCESS) return env()->ThrowError(ToErrorCodeString(r)); } /* We do the call to ares_init_option for caller. */ int optmask = ARES_OPT_FLAGS | ARES_OPT_TIMEOUTMS | ARES_OPT_SOCK_STATE_CB | ARES_OPT_TRIES | ARES_OPT_QUERY_CACHE; if (max_timeout_ > 0) { options.maxtimeout = max_timeout_; optmask |= ARES_OPT_MAXTIMEOUTMS; } r = ares_init_options(&channel_, &options, optmask); if (r != ARES_SUCCESS) { Mutex::ScopedLock lock(ares_library_mutex); ares_library_cleanup(); return env()->ThrowError(ToErrorCodeString(r)); } library_inited_ = true; } void ChannelWrap::StartTimer() { if (timer_handle_ == nullptr) { timer_handle_ = new uv_timer_t(); timer_handle_->data = static_cast(this); uv_timer_init(env()->event_loop(), timer_handle_); } else if (uv_is_active(reinterpret_cast(timer_handle_))) { return; } int timeout = timeout_; if (timeout <= 0 || timeout > 1000) timeout = 1000; uv_timer_start(timer_handle_, AresTimeout, timeout, timeout); } void ChannelWrap::CloseTimer() { if (timer_handle_ == nullptr) return; env()->CloseHandle(timer_handle_, [](uv_timer_t* handle) { delete handle; }); timer_handle_ = nullptr; } ChannelWrap::~ChannelWrap() { ares_destroy(channel_); if (library_inited_) { Mutex::ScopedLock lock(ares_library_mutex); // This decreases the reference counter increased by ares_library_init(). ares_library_cleanup(); } CloseTimer(); } void ChannelWrap::ModifyActivityQueryCount(int count) { active_query_count_ += count; CHECK_GE(active_query_count_, 0); } /** * This function is to check whether current servers are fallback servers * when cares initialized. * * The fallback servers of cares is [ "127.0.0.1" ] with no user additional * setting. */ void ChannelWrap::EnsureServers() { /* if last query is OK or servers are set by user self, do not check */ if (query_last_ok_ || !is_servers_default_) { return; } ares_addr_port_node* servers = nullptr; ares_get_servers_ports(channel_, &servers); /* if no server, ignore */ if (servers == nullptr) return; /* if multi-servers, mark as non-default and ignore */ if (servers->next != nullptr) { ares_free_data(servers); is_servers_default_ = false; return; } /* Check if the only server is a loopback address (IPv4 127.0.0.1 or IPv6 * ::1). Newer c-ares versions may set tcp_port/udp_port to 53 instead of 0, * so we no longer check port values. */ bool is_loopback = false; if (servers[0].family == AF_INET) { is_loopback = (servers[0].addr.addr4.s_addr == htonl(INADDR_LOOPBACK)); } else if (servers[0].family == AF_INET6) { static const unsigned char kIPv6Loopback[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; is_loopback = (memcmp(&servers[0].addr.addr6, kIPv6Loopback, sizeof(kIPv6Loopback)) == 0); } if (!is_loopback) { ares_free_data(servers); is_servers_default_ = false; return; } ares_free_data(servers); servers = nullptr; /* destroy channel and reset channel */ ares_destroy(channel_); CloseTimer(); Setup(); } int AnyTraits::Send(QueryWrap* wrap, const char* name) { wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_ANY); return ARES_SUCCESS; } int ATraits::Send(QueryWrap* wrap, const char* name) { wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_A); return ARES_SUCCESS; } int AaaaTraits::Send(QueryWrap* wrap, const char* name) { wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_AAAA); return ARES_SUCCESS; } int CaaTraits::Send(QueryWrap* wrap, const char* name) { wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_CAA); return ARES_SUCCESS; } int CnameTraits::Send(QueryWrap* wrap, const char* name) { wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_CNAME); return ARES_SUCCESS; } int MxTraits::Send(QueryWrap* wrap, const char* name) { wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_MX); return ARES_SUCCESS; } int NsTraits::Send(QueryWrap* wrap, const char* name) { wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_NS); return ARES_SUCCESS; } int TlsaTraits::Send(QueryWrap* wrap, const char* name) { wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_TLSA); return ARES_SUCCESS; } int TxtTraits::Send(QueryWrap* wrap, const char* name) { wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_TXT); return ARES_SUCCESS; } int SrvTraits::Send(QueryWrap* wrap, const char* name) { wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_SRV); return ARES_SUCCESS; } int PtrTraits::Send(QueryWrap* wrap, const char* name) { wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_PTR); return ARES_SUCCESS; } int NaptrTraits::Send(QueryWrap* wrap, const char* name) { wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_NAPTR); return ARES_SUCCESS; } int SoaTraits::Send(QueryWrap* wrap, const char* name) { wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_SOA); return ARES_SUCCESS; } Maybe AnyTraits::Parse(QueryAnyWrap* wrap, const std::unique_ptr& response) { if (response->is_host) [[unlikely]] { return Just(ARES_EBADRESP); } unsigned char* buf = response->buf.data; int len = response->buf.size; Environment* env = wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); Local ret = Array::New(env->isolate()); int type, status, old_count; /* Parse A records or CNAME records */ ares_addrttl addrttls[256]; int naddrttls = arraysize(addrttls); type = ns_t_cname_or_a; if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls) .To(&status)) { return Nothing(); } uint32_t a_count = ret->Length(); if (status != ARES_SUCCESS && status != ARES_ENODATA) { return Just(status); } if (type == ns_t_a) { CHECK_EQ(static_cast(naddrttls), a_count); auto tmpl = env->a_record_template(); if (tmpl.IsEmpty()) { static constexpr std::string_view names[] = { "address", "ttl", "type", }; tmpl = DictionaryTemplate::New(env->isolate(), names); env->set_a_record_template(tmpl); } MaybeLocal values[] = { Undefined(env->isolate()), // address Undefined(env->isolate()), // ttl Undefined(env->isolate()), // type }; for (uint32_t i = 0; i < a_count; i++) { Local address; if (!ret->Get(env->context(), i).ToLocal(&address)) { return Nothing(); } values[0] = address; values[1] = Integer::NewFromUnsigned(env->isolate(), addrttls[i].ttl); values[2] = env->dns_a_string(); Local obj; if (!NewDictionaryInstance(env->context(), tmpl, values).ToLocal(&obj) || ret->Set(env->context(), i, obj).IsNothing()) { return Nothing(); } } } else { auto tmpl = env->cname_record_template(); if (tmpl.IsEmpty()) { static constexpr std::string_view names[] = { "value", "type", }; tmpl = DictionaryTemplate::New(env->isolate(), names); env->set_cname_record_template(tmpl); } MaybeLocal values[] = { Undefined(env->isolate()), // value Undefined(env->isolate()), // type }; for (uint32_t i = 0; i < a_count; i++) { Local value; if (!ret->Get(env->context(), i).ToLocal(&value)) { return Nothing(); } values[0] = value; values[1] = env->dns_cname_string(); Local obj; if (!NewDictionaryInstance(env->context(), tmpl, values).ToLocal(&obj) || ret->Set(env->context(), i, obj).IsNothing()) { return Nothing(); } } } /* Parse AAAA records */ ares_addr6ttl addr6ttls[256]; int naddr6ttls = arraysize(addr6ttls); type = ns_t_aaaa; if (!ParseGeneralReply(env, buf, len, &type, ret, addr6ttls, &naddr6ttls) .To(&status)) { return Nothing(); } uint32_t aaaa_count = ret->Length() - a_count; if (status != ARES_SUCCESS && status != ARES_ENODATA) return Just(status); CHECK_EQ(aaaa_count, static_cast(naddr6ttls)); CHECK_EQ(ret->Length(), a_count + aaaa_count); auto tmpl = env->aaaa_record_template(); if (tmpl.IsEmpty()) { static constexpr std::string_view names[] = { "address", "ttl", "type", }; tmpl = DictionaryTemplate::New(env->isolate(), names); env->set_aaaa_record_template(tmpl); } MaybeLocal values[] = { Undefined(env->isolate()), // address Undefined(env->isolate()), // ttl Undefined(env->isolate()), // type }; for (uint32_t i = a_count; i < ret->Length(); i++) { Local address; if (!ret->Get(env->context(), i).ToLocal(&address)) { return Nothing(); } values[0] = address; values[1] = Integer::NewFromUnsigned(env->isolate(), addr6ttls[i - a_count].ttl); values[2] = env->dns_aaaa_string(); Local obj; if (!NewDictionaryInstance(env->context(), tmpl, values).ToLocal(&obj) || ret->Set(env->context(), i, obj).IsNothing()) { return Nothing(); } } /* Parse MX records */ if (!ParseMxReply(env, buf, len, ret, true).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS && status != ARES_ENODATA) { return Just(status); } /* Parse NS records */ type = ns_t_ns; old_count = ret->Length(); if (!ParseGeneralReply(env, buf, len, &type, ret).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS && status != ARES_ENODATA) { return Just(status); } auto dns_ns_tmpl = env->dns_ns_record_template(); if (dns_ns_tmpl.IsEmpty()) { static constexpr std::string_view names[] = { "value", "type", }; dns_ns_tmpl = DictionaryTemplate::New(env->isolate(), names); env->set_dns_ns_record_template(dns_ns_tmpl); } MaybeLocal values_ns[] = { Undefined(env->isolate()), // value Undefined(env->isolate()), // type }; for (uint32_t i = old_count; i < ret->Length(); i++) { Local value; if (!ret->Get(env->context(), i).ToLocal(&value)) { return Nothing(); } values_ns[0] = value; values_ns[1] = env->dns_ns_string(); Local obj; if (!NewDictionaryInstance(env->context(), dns_ns_tmpl, values_ns) .ToLocal(&obj) || ret->Set(env->context(), i, obj).IsNothing()) { return Nothing(); } } /* Parse TXT records */ if (!ParseTxtReply(env, buf, len, ret, true).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS && status != ARES_ENODATA) { return Just(status); } /* Parse SRV records */ if (!ParseSrvReply(env, buf, len, ret, true).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS && status != ARES_ENODATA) { return Just(status); } /* Parse PTR records */ type = ns_t_ptr; old_count = ret->Length(); if (!ParseGeneralReply(env, buf, len, &type, ret).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS && status != ARES_ENODATA) return Just(status); for (uint32_t i = old_count; i < ret->Length(); i++) { Local value; if (!ret->Get(env->context(), i).ToLocal(&value)) { return Nothing(); } values_ns[0] = value; values_ns[1] = env->dns_ptr_string(); Local obj; if (!NewDictionaryInstance(env->context(), dns_ns_tmpl, values_ns) .ToLocal(&obj) || ret->Set(env->context(), i, obj).IsNothing()) { return Nothing(); } } /* Parse NAPTR records */ if (!ParseNaptrReply(env, buf, len, ret, true).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS && status != ARES_ENODATA) { return Just(status); } /* Parse SOA records */ Local soa_record = Local(); if (!ParseSoaReply(env, buf, len, &soa_record).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS && status != ARES_ENODATA) { return Just(status); } if (!soa_record.IsEmpty()) { if (ret->Set(env->context(), ret->Length(), soa_record).IsNothing()) { return Just(ARES_ENOMEM); } } /* Parse TLSA records */ if (!ParseTlsaReply(env, buf, len, ret).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS && status != ARES_ENODATA) { return Just(status); } /* Parse CAA records */ if (!ParseCaaReply(env, buf, len, ret, true).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS && status != ARES_ENODATA) { return Just(status); } wrap->CallOnComplete(ret); return Just(ARES_SUCCESS); } Maybe ATraits::Parse(QueryAWrap* wrap, const std::unique_ptr& response) { if (response->is_host) [[unlikely]] { return Just(ARES_EBADRESP); } unsigned char* buf = response->buf.data; int len = response->buf.size; Environment* env = wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); ares_addrttl addrttls[256]; int naddrttls = arraysize(addrttls), status; Local ret = Array::New(env->isolate()); int type = ns_t_a; if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls) .To(&status)) { return Nothing(); } if (status != ARES_SUCCESS) { return Just(status); } Local ttls = AddrTTLToArray(env, addrttls, naddrttls); wrap->CallOnComplete(ret, ttls); return Just(ARES_SUCCESS); } Maybe AaaaTraits::Parse(QueryAaaaWrap* wrap, const std::unique_ptr& response) { if (response->is_host) [[unlikely]] { return Just(ARES_EBADRESP); } unsigned char* buf = response->buf.data; int len = response->buf.size; Environment* env = wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); ares_addr6ttl addrttls[256]; int naddrttls = arraysize(addrttls), status; Local ret = Array::New(env->isolate()); int type = ns_t_aaaa; if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls) .To(&status)) { return Nothing(); } if (status != ARES_SUCCESS) { return Just(status); } Local ttls = AddrTTLToArray(env, addrttls, naddrttls); wrap->CallOnComplete(ret, ttls); return Just(ARES_SUCCESS); } Maybe CaaTraits::Parse(QueryCaaWrap* wrap, const std::unique_ptr& response) { if (response->is_host) [[unlikely]] { return Just(ARES_EBADRESP); } unsigned char* buf = response->buf.data; int len = response->buf.size; Environment* env = wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); Local ret = Array::New(env->isolate()); int status; if (!ParseCaaReply(env, buf, len, ret).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS) { return Just(status); } wrap->CallOnComplete(ret); return Just(ARES_SUCCESS); } Maybe CnameTraits::Parse(QueryCnameWrap* wrap, const std::unique_ptr& response) { if (response->is_host) [[unlikely]] { return Just(ARES_EBADRESP); } unsigned char* buf = response->buf.data; int len = response->buf.size; Environment* env = wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); Local ret = Array::New(env->isolate()); int type = ns_t_cname; int status; if (!ParseGeneralReply(env, buf, len, &type, ret).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS) { return Just(status); } wrap->CallOnComplete(ret); return Just(ARES_SUCCESS); } Maybe MxTraits::Parse(QueryMxWrap* wrap, const std::unique_ptr& response) { if (response->is_host) [[unlikely]] { return Just(ARES_EBADRESP); } unsigned char* buf = response->buf.data; int len = response->buf.size; Environment* env = wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); Local mx_records = Array::New(env->isolate()); int status; if (!ParseMxReply(env, buf, len, mx_records).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS) { return Just(status); } wrap->CallOnComplete(mx_records); return Just(ARES_SUCCESS); } Maybe NsTraits::Parse(QueryNsWrap* wrap, const std::unique_ptr& response) { if (response->is_host) [[unlikely]] { return Just(ARES_EBADRESP); } unsigned char* buf = response->buf.data; int len = response->buf.size; Environment* env = wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); int type = ns_t_ns; Local names = Array::New(env->isolate()); int status; if (!ParseGeneralReply(env, buf, len, &type, names).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS) { return Just(status); } wrap->CallOnComplete(names); return Just(ARES_SUCCESS); } Maybe TlsaTraits::Parse(QueryTlsaWrap* wrap, const std::unique_ptr& response) { if (response->is_host) [[unlikely]] { return Just(ARES_EBADRESP); } unsigned char* buf = response->buf.data; int len = response->buf.size; Environment* env = wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); Local tlsa_records = Array::New(env->isolate()); int status; if (!ParseTlsaReply(env, buf, len, tlsa_records).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS) { return Just(status); } wrap->CallOnComplete(tlsa_records); return Just(ARES_SUCCESS); } Maybe TxtTraits::Parse(QueryTxtWrap* wrap, const std::unique_ptr& response) { if (response->is_host) [[unlikely]] { return Just(ARES_EBADRESP); } unsigned char* buf = response->buf.data; int len = response->buf.size; Environment* env = wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); Local txt_records = Array::New(env->isolate()); int status; if (!ParseTxtReply(env, buf, len, txt_records).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS) { return Just(status); } wrap->CallOnComplete(txt_records); return Just(ARES_SUCCESS); } Maybe SrvTraits::Parse(QuerySrvWrap* wrap, const std::unique_ptr& response) { if (response->is_host) [[unlikely]] { return Just(ARES_EBADRESP); } unsigned char* buf = response->buf.data; int len = response->buf.size; Environment* env = wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); Local srv_records = Array::New(env->isolate()); int status; if (!ParseSrvReply(env, buf, len, srv_records).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS) return Just(status); wrap->CallOnComplete(srv_records); return Just(ARES_SUCCESS); } Maybe PtrTraits::Parse(QueryPtrWrap* wrap, const std::unique_ptr& response) { if (response->is_host) [[unlikely]] { return Just(ARES_EBADRESP); } unsigned char* buf = response->buf.data; int len = response->buf.size; Environment* env = wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); int type = ns_t_ptr; Local aliases = Array::New(env->isolate()); int status; if (!ParseGeneralReply(env, buf, len, &type, aliases).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS) { return Just(status); } wrap->CallOnComplete(aliases); return Just(ARES_SUCCESS); } Maybe NaptrTraits::Parse(QueryNaptrWrap* wrap, const std::unique_ptr& response) { if (response->is_host) [[unlikely]] { return Just(ARES_EBADRESP); } unsigned char* buf = response->buf.data; int len = response->buf.size; Environment* env = wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); Local naptr_records = Array::New(env->isolate()); int status; if (!ParseNaptrReply(env, buf, len, naptr_records).To(&status)) { return Nothing(); } if (status != ARES_SUCCESS) { return Just(status); } wrap->CallOnComplete(naptr_records); return Just(ARES_SUCCESS); } Maybe SoaTraits::Parse(QuerySoaWrap* wrap, const std::unique_ptr& response) { if (response->is_host) [[unlikely]] { return Just(ARES_EBADRESP); } unsigned char* buf = response->buf.data; int len = response->buf.size; Environment* env = wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); auto tmpl = getSoaRecordTemplate(env); MaybeLocal values[] = { Undefined(env->isolate()), // nsname Undefined(env->isolate()), // hostmaster Undefined(env->isolate()), // serial Undefined(env->isolate()), // refresh Undefined(env->isolate()), // retry Undefined(env->isolate()), // expire Undefined(env->isolate()), // minttl Undefined(env->isolate()), // type }; ares_soa_reply* soa_out; int status = ares_parse_soa_reply(buf, len, &soa_out); if (status != ARES_SUCCESS) return Just(status); auto cleanup = OnScopeLeave([&]() { ares_free_data(soa_out); }); values[0] = OneByteString(env->isolate(), soa_out->nsname); values[1] = OneByteString(env->isolate(), soa_out->hostmaster); values[2] = Integer::NewFromUnsigned(env->isolate(), soa_out->serial); values[3] = Integer::New(env->isolate(), soa_out->refresh); values[4] = Integer::New(env->isolate(), soa_out->retry); values[5] = Integer::New(env->isolate(), soa_out->expire); values[6] = Integer::NewFromUnsigned(env->isolate(), soa_out->minttl); Local soa_record; if (!NewDictionaryInstance(env->context(), tmpl, values) .ToLocal(&soa_record)) { return Nothing(); } wrap->CallOnComplete(soa_record); return Just(ARES_SUCCESS); } int ReverseTraits::Send(QueryReverseWrap* wrap, const char* name) { permission::PermissionScope scope = permission::PermissionScope::kNet; Environment* env_holder = wrap->env(); if (!env_holder->permission()->is_granted(env_holder, scope, name)) [[unlikely]] { wrap->QueuePermissionModelResponseCallback(name); // Error will be returned in the callback return ARES_SUCCESS; } int length, family; char address_buffer[sizeof(struct in6_addr)]; if (uv_inet_pton(AF_INET, name, &address_buffer) == 0) { length = sizeof(struct in_addr); family = AF_INET; } else if (uv_inet_pton(AF_INET6, name, &address_buffer) == 0) { length = sizeof(struct in6_addr); family = AF_INET6; } else { return UV_EINVAL; // So errnoException() reports a proper error. } TRACE_EVENT_NESTABLE_ASYNC_BEGIN2( TRACING_CATEGORY_NODE2(dns, native), "reverse", wrap, "name", TRACE_STR_COPY(name), "family", family == AF_INET ? "ipv4" : "ipv6"); ares_gethostbyaddr(wrap->channel()->cares_channel(), address_buffer, length, family, QueryReverseWrap::Callback, wrap->MakeCallbackPointer()); return ARES_SUCCESS; } Maybe ReverseTraits::Parse(QueryReverseWrap* wrap, const std::unique_ptr& response) { if (!response->is_host) [[unlikely]] { return Just(ARES_EBADRESP); } struct hostent* host = response->host.get(); Environment* env = wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); Local names; if (!HostentToNames(env, host).ToLocal(&names)) { return Nothing(); } wrap->CallOnComplete(names); return Just(ARES_SUCCESS); } namespace { template static void Query(const FunctionCallbackInfo& args) { ChannelWrap* channel; ASSIGN_OR_RETURN_UNWRAP(&channel, args.This()); CHECK_EQ(false, args.IsConstructCall()); CHECK(args[0]->IsObject()); CHECK(args[1]->IsString()); Local req_wrap_obj = args[0].As(); Local string = args[1].As(); auto wrap = std::make_unique(channel, req_wrap_obj); node::Utf8Value utf8name(args.GetIsolate(), string); auto plain_name = utf8name.ToStringView(); std::string name = ada::idna::to_ascii(plain_name); // Ensure c-ares did not fall back to loopback resolver. channel->EnsureServers(); channel->ModifyActivityQueryCount(1); int err = wrap->Send(name.c_str()); if (err) { channel->ModifyActivityQueryCount(-1); } else { // Release ownership of the pointer allowing the ownership to be transferred USE(wrap.release()); } args.GetReturnValue().Set(err); } void AfterGetAddrInfo(uv_getaddrinfo_t* req, int status, struct addrinfo* res) { auto cleanup = OnScopeLeave([&]() { uv_freeaddrinfo(res); }); BaseObjectPtr req_wrap{ static_cast(req->data)}; Environment* env = req_wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); Local argv[] = { Integer::New(env->isolate(), status), Null(env->isolate()) }; uint32_t n = 0; const uint8_t order = req_wrap->order(); if (status == 0) { Local results = Array::New(env->isolate()); auto add = [&](bool want_ipv4, bool want_ipv6) -> Maybe { for (auto p = res; p != nullptr; p = p->ai_next) { CHECK_EQ(p->ai_socktype, SOCK_STREAM); const char* addr; if (want_ipv4 && p->ai_family == AF_INET) { addr = reinterpret_cast( &(reinterpret_cast(p->ai_addr)->sin_addr)); } else if (want_ipv6 && p->ai_family == AF_INET6) { addr = reinterpret_cast( &(reinterpret_cast(p->ai_addr)->sin6_addr)); } else { continue; } char ip[INET6_ADDRSTRLEN]; if (uv_inet_ntop(p->ai_family, addr, ip, sizeof(ip))) continue; Local s = OneByteString(env->isolate(), ip); if (results->Set(env->context(), n, s).IsNothing()) return Nothing(); n++; } return JustVoid(); }; switch (order) { case DNS_ORDER_IPV4_FIRST: if (add(true, false).IsNothing() || add(false, true).IsNothing()) return; break; case DNS_ORDER_IPV6_FIRST: if (add(false, true).IsNothing() || add(true, false).IsNothing()) return; break; default: if (add(true, true).IsNothing()) return; break; } // No responses were found to return if (n == 0) { argv[0] = Integer::New(env->isolate(), UV_EAI_NODATA); } argv[1] = results; } TRACE_EVENT_NESTABLE_ASYNC_END2(TRACING_CATEGORY_NODE2(dns, native), "lookup", req_wrap.get(), "count", n, "order", order); // Make the callback into JavaScript req_wrap->MakeCallback(env->oncomplete_string(), arraysize(argv), argv); } void AfterGetNameInfo(uv_getnameinfo_t* req, int status, const char* hostname, const char* service) { BaseObjectPtr req_wrap{ static_cast(req->data)}; Environment* env = req_wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); Local argv[] = { Integer::New(env->isolate(), status), Null(env->isolate()), Null(env->isolate()) }; if (status == 0) { // Success Local js_hostname = OneByteString(env->isolate(), hostname); Local js_service = OneByteString(env->isolate(), service); argv[1] = js_hostname; argv[2] = js_service; } TRACE_EVENT_NESTABLE_ASYNC_END2( TRACING_CATEGORY_NODE2(dns, native), "lookupService", req_wrap.get(), "hostname", TRACE_STR_COPY(hostname), "service", TRACE_STR_COPY(service)); // Make the callback into JavaScript req_wrap->MakeCallback(env->oncomplete_string(), arraysize(argv), argv); } void CanonicalizeIP(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); node::Utf8Value ip(isolate, args[0]); int af; unsigned char result[sizeof(ares_addr_port_node::addr)]; if (uv_inet_pton(af = AF_INET, *ip, result) != 0 && uv_inet_pton(af = AF_INET6, *ip, result) != 0) return; char canonical_ip[INET6_ADDRSTRLEN]{}; CHECK_EQ(0, uv_inet_ntop(af, result, canonical_ip, sizeof(canonical_ip))); args.GetReturnValue().Set(OneByteString(isolate, canonical_ip)); } void ConvertIpv6StringToBuffer(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); node::Utf8Value ip(isolate, args[0]); unsigned char dst[16]; // IPv6 addresses are 128 bits (16 bytes) if (uv_inet_pton(AF_INET6, *ip, dst) != 0) { isolate->ThrowException(Exception::Error( FIXED_ONE_BYTE_STRING(isolate, "Invalid IPv6 address"))); return; } Local buffer; if (node::Buffer::Copy( isolate, reinterpret_cast(dst), sizeof(dst)) .ToLocal(&buffer)) { args.GetReturnValue().Set(buffer); } } void GetAddrInfo(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsObject()); CHECK(args[1]->IsString()); CHECK(args[2]->IsInt32()); CHECK(args[4]->IsUint32()); Local req_wrap_obj = args[0].As(); node::Utf8Value hostname(env->isolate(), args[1]); ERR_ACCESS_DENIED_IF_INSUFFICIENT_PERMISSIONS( env, permission::PermissionScope::kNet, hostname.ToStringView(), args); std::string ascii_hostname = ada::idna::to_ascii(hostname.ToStringView()); int32_t flags = 0; if (args[3]->IsInt32()) { flags = args[3].As()->Value(); } int family; switch (args[2].As()->Value()) { case 0: family = AF_UNSPEC; break; case 4: family = AF_INET; break; case 6: family = AF_INET6; break; default: UNREACHABLE("bad address family"); } Local order = args[4].As(); auto req_wrap = std::make_unique(env, req_wrap_obj, order->Value()); struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = flags; TRACE_EVENT_NESTABLE_ASYNC_BEGIN2(TRACING_CATEGORY_NODE2(dns, native), "lookup", req_wrap.get(), "hostname", TRACE_STR_COPY(ascii_hostname.data()), "family", family == AF_INET ? "ipv4" : family == AF_INET6 ? "ipv6" : "unspec"); int err = req_wrap->Dispatch( uv_getaddrinfo, AfterGetAddrInfo, ascii_hostname.data(), nullptr, &hints); if (err == 0) // Release ownership of the pointer allowing the ownership to be transferred USE(req_wrap.release()); args.GetReturnValue().Set(err); } void GetNameInfo(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsObject()); CHECK(args[1]->IsString()); CHECK(args[2]->IsUint32()); Local req_wrap_obj = args[0].As(); node::Utf8Value ip(env->isolate(), args[1]); const unsigned port = args[2]->Uint32Value(env->context()).FromJust(); struct sockaddr_storage addr; CHECK(uv_ip4_addr(*ip, port, reinterpret_cast(&addr)) == 0 || uv_ip6_addr(*ip, port, reinterpret_cast(&addr)) == 0); auto req_wrap = std::make_unique(env, req_wrap_obj); TRACE_EVENT_NESTABLE_ASYNC_BEGIN2( TRACING_CATEGORY_NODE2(dns, native), "lookupService", req_wrap.get(), "ip", TRACE_STR_COPY(*ip), "port", port); int err = 0; if (!env->permission()->is_granted( env, permission::PermissionScope::kNet, ip.ToStringView())) [[unlikely]] { req_wrap->InsufficientPermissionError(*ip); } else { err = req_wrap->Dispatch(uv_getnameinfo, AfterGetNameInfo, reinterpret_cast(&addr), NI_NAMEREQD); } if (err == 0) // Release ownership of the pointer allowing the ownership to be transferred USE(req_wrap.release()); args.GetReturnValue().Set(err); } void GetServers(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ChannelWrap* channel; ASSIGN_OR_RETURN_UNWRAP(&channel, args.This()); Local server_array = Array::New(env->isolate()); ares_addr_port_node* servers; int r = ares_get_servers_ports(channel->cares_channel(), &servers); CHECK_EQ(r, ARES_SUCCESS); auto cleanup = OnScopeLeave([&]() { ares_free_data(servers); }); ares_addr_port_node* cur = servers; for (uint32_t i = 0; cur != nullptr; ++i, cur = cur->next) { char ip[INET6_ADDRSTRLEN]; const void* caddr = static_cast(&cur->addr); int err = uv_inet_ntop(cur->family, caddr, ip, sizeof(ip)); CHECK_EQ(err, 0); Local ret[] = { OneByteString(env->isolate(), ip), Integer::New(env->isolate(), cur->udp_port) }; if (server_array->Set(env->context(), i, Array::New(env->isolate(), ret, arraysize(ret))) .IsNothing()) { return; } } args.GetReturnValue().Set(server_array); } void SetServers(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ChannelWrap* channel; ASSIGN_OR_RETURN_UNWRAP(&channel, args.This()); if (channel->active_query_count()) { return args.GetReturnValue().Set(DNS_ESETSRVPENDING); } CHECK(args[0]->IsArray()); Local arr = args[0].As(); uint32_t len = arr->Length(); if (len == 0) { int rv = ares_set_servers(channel->cares_channel(), nullptr); return args.GetReturnValue().Set(rv); } std::vector servers(len); ares_addr_port_node* last = nullptr; int err; for (uint32_t i = 0; i < len; i++) { Local val; if (!arr->Get(env->context(), i).ToLocal(&val)) return; CHECK(val->IsArray()); Local elm = val.As(); Local familyValue; Local ipValue; Local portValue; if (!elm->Get(env->context(), 0).ToLocal(&familyValue)) return; if (!elm->Get(env->context(), 1).ToLocal(&ipValue)) return; if (!elm->Get(env->context(), 2).ToLocal(&portValue)) return; CHECK(familyValue->Int32Value(env->context()).FromJust()); CHECK(ipValue->IsString()); CHECK(portValue->Int32Value(env->context()).FromJust()); int fam = familyValue->Int32Value(env->context()).FromJust(); node::Utf8Value ip(env->isolate(), ipValue); int port = portValue->Int32Value(env->context()).FromJust(); ares_addr_port_node* cur = &servers[i]; cur->tcp_port = cur->udp_port = port; switch (fam) { case 4: cur->family = AF_INET; err = uv_inet_pton(AF_INET, *ip, &cur->addr); break; case 6: cur->family = AF_INET6; err = uv_inet_pton(AF_INET6, *ip, &cur->addr); break; default: UNREACHABLE("Bad address family"); } if (err) break; cur->next = nullptr; if (last != nullptr) last->next = cur; last = cur; } if (err == 0) err = ares_set_servers_ports(channel->cares_channel(), servers.data()); else err = ARES_EBADSTR; if (err == ARES_SUCCESS) channel->set_is_servers_default(false); args.GetReturnValue().Set(err); } void SetLocalAddress(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ChannelWrap* channel; ASSIGN_OR_RETURN_UNWRAP(&channel, args.This()); CHECK_EQ(args.Length(), 2); CHECK(args[0]->IsString()); Isolate* isolate = args.GetIsolate(); node::Utf8Value ip0(isolate, args[0]); unsigned char addr0[sizeof(struct in6_addr)]; unsigned char addr1[sizeof(struct in6_addr)]; int type0 = 0; // This function accepts 2 arguments. The first may be either an IPv4 // address or an IPv6 address. If present, the second argument must be the // other type of address. Otherwise, the unspecified type of IP is set // to 0 (any). if (uv_inet_pton(AF_INET, *ip0, &addr0) == 0) { ares_set_local_ip4(channel->cares_channel(), nbytes::ReadUint32BE(addr0)); type0 = 4; } else if (uv_inet_pton(AF_INET6, *ip0, &addr0) == 0) { ares_set_local_ip6(channel->cares_channel(), addr0); type0 = 6; } else { THROW_ERR_INVALID_ARG_VALUE(env, "Invalid IP address."); return; } if (!args[1]->IsUndefined()) { CHECK(args[1]->IsString()); node::Utf8Value ip1(isolate, args[1]); if (uv_inet_pton(AF_INET, *ip1, &addr1) == 0) { if (type0 == 4) { THROW_ERR_INVALID_ARG_VALUE(env, "Cannot specify two IPv4 addresses."); return; } else { ares_set_local_ip4(channel->cares_channel(), nbytes::ReadUint32BE(addr1)); } } else if (uv_inet_pton(AF_INET6, *ip1, &addr1) == 0) { if (type0 == 6) { THROW_ERR_INVALID_ARG_VALUE(env, "Cannot specify two IPv6 addresses."); return; } else { ares_set_local_ip6(channel->cares_channel(), addr1); } } else { THROW_ERR_INVALID_ARG_VALUE(env, "Invalid IP address."); return; } } else { // No second arg specified if (type0 == 4) { memset(&addr1, 0, sizeof(addr1)); ares_set_local_ip6(channel->cares_channel(), addr1); } else { ares_set_local_ip4(channel->cares_channel(), 0); } } } void Cancel(const FunctionCallbackInfo& args) { ChannelWrap* channel; ASSIGN_OR_RETURN_UNWRAP(&channel, args.This()); TRACE_EVENT_INSTANT0(TRACING_CATEGORY_NODE2(dns, native), "cancel", TRACE_EVENT_SCOPE_THREAD); ares_cancel(channel->cares_channel()); } const char EMSG_ESETSRVPENDING[] = "There are pending queries."; void StrError(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); int code = args[0]->Int32Value(env->context()).FromJust(); const char* errmsg = (code == DNS_ESETSRVPENDING) ? EMSG_ESETSRVPENDING : ares_strerror(code); args.GetReturnValue().Set(OneByteString(env->isolate(), errmsg)); } } // namespace inline void safe_free_hostent(struct hostent* host) { int idx; if (host->h_addr_list != nullptr) { idx = 0; while (host->h_addr_list[idx]) { free(host->h_addr_list[idx++]); } free(host->h_addr_list); host->h_addr_list = nullptr; } if (host->h_aliases != nullptr) { idx = 0; while (host->h_aliases[idx]) { free(host->h_aliases[idx++]); } free(host->h_aliases); host->h_aliases = nullptr; } free(host->h_name); free(host); } void Initialize(Local target, Local unused, Local context, void* priv) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); SetMethod(context, target, "getaddrinfo", GetAddrInfo); SetMethod(context, target, "getnameinfo", GetNameInfo); SetMethodNoSideEffect(context, target, "canonicalizeIP", CanonicalizeIP); SetMethodNoSideEffect( context, target, "convertIpv6StringToBuffer", ConvertIpv6StringToBuffer); SetMethod(context, target, "strerror", StrError); NODE_DEFINE_CONSTANT(target, AF_INET); NODE_DEFINE_CONSTANT(target, AF_INET6); NODE_DEFINE_CONSTANT(target, AF_UNSPEC); NODE_DEFINE_CONSTANT(target, AI_ADDRCONFIG); NODE_DEFINE_CONSTANT(target, AI_ALL); NODE_DEFINE_CONSTANT(target, AI_V4MAPPED); NODE_DEFINE_CONSTANT(target, DNS_ORDER_VERBATIM); NODE_DEFINE_CONSTANT(target, DNS_ORDER_IPV4_FIRST); NODE_DEFINE_CONSTANT(target, DNS_ORDER_IPV6_FIRST); Local aiw = BaseObject::MakeLazilyInitializedJSTemplate(env); aiw->Inherit(AsyncWrap::GetConstructorTemplate(env)); SetConstructorFunction(context, target, "GetAddrInfoReqWrap", aiw); Local niw = BaseObject::MakeLazilyInitializedJSTemplate(env); niw->Inherit(AsyncWrap::GetConstructorTemplate(env)); SetConstructorFunction(context, target, "GetNameInfoReqWrap", niw); Local qrw = BaseObject::MakeLazilyInitializedJSTemplate(env); qrw->Inherit(AsyncWrap::GetConstructorTemplate(env)); SetConstructorFunction(context, target, "QueryReqWrap", qrw); Local channel_wrap = NewFunctionTemplate(isolate, ChannelWrap::New); channel_wrap->InstanceTemplate()->SetInternalFieldCount( ChannelWrap::kInternalFieldCount); channel_wrap->Inherit(AsyncWrap::GetConstructorTemplate(env)); #define V(Name, _, JS) \ SetProtoMethod(isolate, channel_wrap, #JS, Query); QUERY_TYPES(V) #undef V SetProtoMethodNoSideEffect(isolate, channel_wrap, "getServers", GetServers); SetProtoMethod(isolate, channel_wrap, "setServers", SetServers); SetProtoMethod(isolate, channel_wrap, "setLocalAddress", SetLocalAddress); SetProtoMethod(isolate, channel_wrap, "cancel", Cancel); SetConstructorFunction(context, target, "ChannelWrap", channel_wrap); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(GetAddrInfo); registry->Register(GetNameInfo); registry->Register(CanonicalizeIP); registry->Register(ConvertIpv6StringToBuffer); registry->Register(StrError); registry->Register(ChannelWrap::New); #define V(Name, _, __) registry->Register(Query); QUERY_TYPES(V) #undef V registry->Register(GetServers); registry->Register(SetServers); registry->Register(SetLocalAddress); registry->Register(Cancel); } } // namespace cares_wrap } // namespace node NODE_BINDING_CONTEXT_AWARE_INTERNAL(cares_wrap, node::cares_wrap::Initialize) NODE_BINDING_EXTERNAL_REFERENCE(cares_wrap, node::cares_wrap::RegisterExternalReferences)