| // 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 <cerrno> |
| #include <cstring> |
| #include <memory> |
| #include <vector> |
| #include <unordered_set> |
| |
| #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<uint32_t>(p[0] << 8U) | (static_cast<uint32_t>(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<NodeAresTask> 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<ChannelWrap*>(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<Array> HostentToNames(Environment* env, struct hostent* host) { |
| EscapableHandleScope scope(env->isolate()); |
| |
| LocalVector<Value> 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<Array> HostentToNames(Environment* env, |
| struct hostent* host, |
| Local<Array> 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 <typename T> |
| Local<Array> AddrTTLToArray( |
| Environment* env, |
| const T* addrttls, |
| size_t naddrttls) { |
| MaybeStackBuffer<Local<Value>, 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<int> ParseGeneralReply(Environment* env, |
| const unsigned char* buf, |
| int len, |
| int* type, |
| Local<Array> 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<ares_addrttl*>(addrttls), |
| naddrttls); |
| break; |
| case ns_t_aaaa: |
| status = ares_parse_aaaa_reply(buf, |
| len, |
| &host, |
| static_cast<ares_addr6ttl*>(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<int>(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<int>(); |
| } |
| return Just<int>(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<int>(); |
| } |
| } 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<int>(); |
| } |
| } |
| } 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<int>(); |
| } |
| } |
| } |
| |
| return Just<int>(ARES_SUCCESS); |
| } |
| Maybe<int> ParseMxReply(Environment* env, |
| const unsigned char* buf, |
| int len, |
| Local<Array> 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<int>(status); |
| |
| DeleteFnPtr<void, ares_free_data> free_me(mx_start); |
| |
| uint32_t offset = ret->Length(); |
| ares_mx_reply* current = mx_start; |
| |
| MaybeLocal<Value> 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<Value> record; |
| if (!NewDictionaryInstance(env->context(), tmpl, values).ToLocal(&record) || |
| ret->Set(env->context(), i + offset, record).IsNothing()) { |
| return Nothing<int>(); |
| } |
| } |
| |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> ParseCaaReply(Environment* env, |
| const unsigned char* buf, |
| int len, |
| Local<Array> 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<int>(status); |
| DeleteFnPtr<void, ares_free_data> free_me(caa_start); |
| |
| MaybeLocal<Value> 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<Object> caa_record; |
| if (!NewDictionaryInstance(env->context(), tmpl, values) |
| .ToLocal(&caa_record)) { |
| return Nothing<int>(); |
| } |
| |
| // 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<int>(); |
| } |
| |
| if (ret->Set(env->context(), i + offset, caa_record).IsNothing()) { |
| return Nothing<int>(); |
| } |
| } |
| |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> ParseTlsaReply(Environment* env, |
| unsigned char* buf, |
| int len, |
| Local<Array> 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<int>(status); |
| } |
| |
| DeleteFnPtr<ares_dns_record_t, ares_dns_record_destroy> free_me(dnsrec); |
| |
| uint32_t offset = ret->Length(); |
| size_t rr_count = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); |
| |
| MaybeLocal<Value> 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<ArrayBuffer> 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<Object> tlsa_rec; |
| if (!NewDictionaryInstance(env->context(), tmpl, values) |
| .ToLocal(&tlsa_rec) || |
| ret->Set(env->context(), offset + i, tlsa_rec).IsNothing()) { |
| return Nothing<int>(); |
| } |
| } |
| |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> ParseTxtReply(Environment* env, |
| const unsigned char* buf, |
| int len, |
| Local<Array> 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<int>(status); |
| DeleteFnPtr<void, ares_free_data> free_me(txt_out); |
| |
| Local<Array> txt_chunk; |
| LocalVector<Value> chunks(env->isolate()); |
| |
| struct ares_txt_ext* current = txt_out; |
| uint32_t i = 0; |
| uint32_t offset = ret->Length(); |
| |
| MaybeLocal<Value> values[] = { |
| Undefined(env->isolate()), // entries |
| Undefined(env->isolate()), // type |
| }; |
| |
| for (; current != nullptr; current = current->next) { |
| Local<String> 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<Object> elem; |
| if (!NewDictionaryInstance(env->context(), tmpl, values) |
| .ToLocal(&elem) || |
| ret->Set(env->context(), offset + i++, elem).IsNothing()) { |
| return Nothing<int>(); |
| } |
| } else if (ret->Set(env->context(), offset + i++, txt_chunk) |
| .IsNothing()) { |
| return Nothing<int>(); |
| } |
| } |
| |
| 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<Object> elem; |
| if (!NewDictionaryInstance(env->context(), tmpl, values).ToLocal(&elem) || |
| ret->Set(env->context(), offset + i, elem).IsNothing()) { |
| return Nothing<int>(); |
| } |
| } else if (ret->Set(env->context(), offset + i, txt_chunk).IsNothing()) { |
| return Nothing<int>(); |
| } |
| } |
| |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> ParseSrvReply(Environment* env, |
| const unsigned char* buf, |
| int len, |
| Local<Array> 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<int>(status); |
| DeleteFnPtr<void, ares_free_data> free_me(srv_start); |
| |
| MaybeLocal<Value> 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<Object> srv_record; |
| if (!NewDictionaryInstance(env->context(), tmpl, values) |
| .ToLocal(&srv_record) || |
| ret->Set(env->context(), i + offset, srv_record).IsNothing()) { |
| return Nothing<int>(); |
| } |
| } |
| |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> ParseNaptrReply(Environment* env, |
| const unsigned char* buf, |
| int len, |
| Local<Array> 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<int>(status); |
| DeleteFnPtr<void, ares_free_data> free_me(naptr_start); |
| |
| MaybeLocal<Value> 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<Object> naptr_record; |
| if (!NewDictionaryInstance(env->context(), tmpl, values) |
| .ToLocal(&naptr_record) || |
| ret->Set(env->context(), i + offset, naptr_record).IsNothing()) { |
| return Nothing<int>(); |
| } |
| } |
| |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Local<DictionaryTemplate> 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<int> ParseSoaReply(Environment* env, |
| unsigned char* buf, |
| int len, |
| Local<Object>* 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<char[], AresDeleter>; |
| |
| // 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<int>(status == ARES_EBADNAME ? ARES_EBADRESP : status); |
| } |
| |
| const ares_unique_ptr name(name_temp); |
| |
| if (ptr + temp_len + NS_QFIXEDSZ > buf + len) { |
| return Just<int>(ARES_EBADRESP); |
| } |
| ptr += temp_len + NS_QFIXEDSZ; |
| |
| MaybeLocal<Value> 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<int>(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<int>(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<int>(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<int>(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<int>(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<Object> soa_record; |
| if (!NewDictionaryInstance(env->context(), tmpl, values) |
| .ToLocal(&soa_record)) { |
| return Nothing<int>(); |
| } |
| |
| *ret = handle_scope.Escape(soa_record); |
| break; |
| } |
| |
| ptr += rr_len; |
| } |
| |
| return Just<int>(ARES_SUCCESS); |
| } |
| } // anonymous namespace |
| |
| ChannelWrap::ChannelWrap(Environment* env, |
| Local<Object> 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<Value>& 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<Int32>()->Value(); |
| const int tries = args[1].As<Int32>()->Value(); |
| const int max_timeout = args[2].As<Int32>()->Value(); |
| Environment* env = Environment::GetCurrent(args); |
| new ChannelWrap(env, args.This(), timeout, tries, max_timeout); |
| } |
| |
| GetAddrInfoReqWrap::GetAddrInfoReqWrap(Environment* env, |
| Local<Object> req_wrap_obj, |
| uint8_t order) |
| : ReqWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_GETADDRINFOREQWRAP), |
| order_(order) {} |
| |
| GetNameInfoReqWrap::GetNameInfoReqWrap( |
| Environment* env, |
| Local<Object> 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<ChannelWrap*>(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<void*>(this); |
| uv_timer_init(env()->event_loop(), timer_handle_); |
| } else if (uv_is_active(reinterpret_cast<uv_handle_t*>(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<AnyTraits>* wrap, const char* name) { |
| wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_ANY); |
| return ARES_SUCCESS; |
| } |
| |
| int ATraits::Send(QueryWrap<ATraits>* wrap, const char* name) { |
| wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_A); |
| return ARES_SUCCESS; |
| } |
| |
| int AaaaTraits::Send(QueryWrap<AaaaTraits>* wrap, const char* name) { |
| wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_AAAA); |
| return ARES_SUCCESS; |
| } |
| |
| int CaaTraits::Send(QueryWrap<CaaTraits>* wrap, const char* name) { |
| wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_CAA); |
| return ARES_SUCCESS; |
| } |
| |
| int CnameTraits::Send(QueryWrap<CnameTraits>* wrap, const char* name) { |
| wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_CNAME); |
| return ARES_SUCCESS; |
| } |
| |
| int MxTraits::Send(QueryWrap<MxTraits>* wrap, const char* name) { |
| wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_MX); |
| return ARES_SUCCESS; |
| } |
| |
| int NsTraits::Send(QueryWrap<NsTraits>* wrap, const char* name) { |
| wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_NS); |
| return ARES_SUCCESS; |
| } |
| |
| int TlsaTraits::Send(QueryWrap<TlsaTraits>* wrap, const char* name) { |
| wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_TLSA); |
| return ARES_SUCCESS; |
| } |
| |
| int TxtTraits::Send(QueryWrap<TxtTraits>* wrap, const char* name) { |
| wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_TXT); |
| return ARES_SUCCESS; |
| } |
| |
| int SrvTraits::Send(QueryWrap<SrvTraits>* wrap, const char* name) { |
| wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_SRV); |
| return ARES_SUCCESS; |
| } |
| |
| int PtrTraits::Send(QueryWrap<PtrTraits>* wrap, const char* name) { |
| wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_PTR); |
| return ARES_SUCCESS; |
| } |
| |
| int NaptrTraits::Send(QueryWrap<NaptrTraits>* wrap, const char* name) { |
| wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_NAPTR); |
| return ARES_SUCCESS; |
| } |
| |
| int SoaTraits::Send(QueryWrap<SoaTraits>* wrap, const char* name) { |
| wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_SOA); |
| return ARES_SUCCESS; |
| } |
| |
| Maybe<int> AnyTraits::Parse(QueryAnyWrap* wrap, |
| const std::unique_ptr<ResponseData>& response) { |
| if (response->is_host) [[unlikely]] { |
| return Just<int>(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<Array> 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<int>(); |
| } |
| uint32_t a_count = ret->Length(); |
| if (status != ARES_SUCCESS && status != ARES_ENODATA) { |
| return Just<int>(status); |
| } |
| |
| if (type == ns_t_a) { |
| CHECK_EQ(static_cast<uint32_t>(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<Value> values[] = { |
| Undefined(env->isolate()), // address |
| Undefined(env->isolate()), // ttl |
| Undefined(env->isolate()), // type |
| }; |
| |
| for (uint32_t i = 0; i < a_count; i++) { |
| Local<Value> address; |
| if (!ret->Get(env->context(), i).ToLocal(&address)) { |
| return Nothing<int>(); |
| } |
| values[0] = address; |
| values[1] = Integer::NewFromUnsigned(env->isolate(), addrttls[i].ttl); |
| values[2] = env->dns_a_string(); |
| |
| Local<Object> obj; |
| if (!NewDictionaryInstance(env->context(), tmpl, values).ToLocal(&obj) || |
| ret->Set(env->context(), i, obj).IsNothing()) { |
| return Nothing<int>(); |
| } |
| } |
| } 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<Value> values[] = { |
| Undefined(env->isolate()), // value |
| Undefined(env->isolate()), // type |
| }; |
| for (uint32_t i = 0; i < a_count; i++) { |
| Local<Value> value; |
| if (!ret->Get(env->context(), i).ToLocal(&value)) { |
| return Nothing<int>(); |
| } |
| values[0] = value; |
| values[1] = env->dns_cname_string(); |
| Local<Object> obj; |
| if (!NewDictionaryInstance(env->context(), tmpl, values).ToLocal(&obj) || |
| ret->Set(env->context(), i, obj).IsNothing()) { |
| return Nothing<int>(); |
| } |
| } |
| } |
| |
| /* 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<int>(); |
| } |
| uint32_t aaaa_count = ret->Length() - a_count; |
| if (status != ARES_SUCCESS && status != ARES_ENODATA) |
| return Just<int>(status); |
| |
| CHECK_EQ(aaaa_count, static_cast<uint32_t>(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<Value> values[] = { |
| Undefined(env->isolate()), // address |
| Undefined(env->isolate()), // ttl |
| Undefined(env->isolate()), // type |
| }; |
| |
| for (uint32_t i = a_count; i < ret->Length(); i++) { |
| Local<Value> address; |
| if (!ret->Get(env->context(), i).ToLocal(&address)) { |
| return Nothing<int>(); |
| } |
| values[0] = address; |
| values[1] = |
| Integer::NewFromUnsigned(env->isolate(), addr6ttls[i - a_count].ttl); |
| values[2] = env->dns_aaaa_string(); |
| Local<Object> obj; |
| if (!NewDictionaryInstance(env->context(), tmpl, values).ToLocal(&obj) || |
| ret->Set(env->context(), i, obj).IsNothing()) { |
| return Nothing<int>(); |
| } |
| } |
| |
| /* Parse MX records */ |
| if (!ParseMxReply(env, buf, len, ret, true).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS && status != ARES_ENODATA) { |
| return Just<int>(status); |
| } |
| |
| /* Parse NS records */ |
| type = ns_t_ns; |
| old_count = ret->Length(); |
| if (!ParseGeneralReply(env, buf, len, &type, ret).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS && status != ARES_ENODATA) { |
| return Just<int>(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<Value> values_ns[] = { |
| Undefined(env->isolate()), // value |
| Undefined(env->isolate()), // type |
| }; |
| |
| for (uint32_t i = old_count; i < ret->Length(); i++) { |
| Local<Value> value; |
| if (!ret->Get(env->context(), i).ToLocal(&value)) { |
| return Nothing<int>(); |
| } |
| values_ns[0] = value; |
| values_ns[1] = env->dns_ns_string(); |
| Local<Object> obj; |
| if (!NewDictionaryInstance(env->context(), dns_ns_tmpl, values_ns) |
| .ToLocal(&obj) || |
| ret->Set(env->context(), i, obj).IsNothing()) { |
| return Nothing<int>(); |
| } |
| } |
| |
| /* Parse TXT records */ |
| if (!ParseTxtReply(env, buf, len, ret, true).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS && status != ARES_ENODATA) { |
| return Just<int>(status); |
| } |
| |
| /* Parse SRV records */ |
| if (!ParseSrvReply(env, buf, len, ret, true).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS && status != ARES_ENODATA) { |
| return Just<int>(status); |
| } |
| |
| /* Parse PTR records */ |
| type = ns_t_ptr; |
| old_count = ret->Length(); |
| if (!ParseGeneralReply(env, buf, len, &type, ret).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS && status != ARES_ENODATA) |
| return Just<int>(status); |
| |
| for (uint32_t i = old_count; i < ret->Length(); i++) { |
| Local<Value> value; |
| if (!ret->Get(env->context(), i).ToLocal(&value)) { |
| return Nothing<int>(); |
| } |
| values_ns[0] = value; |
| values_ns[1] = env->dns_ptr_string(); |
| Local<Object> obj; |
| if (!NewDictionaryInstance(env->context(), dns_ns_tmpl, values_ns) |
| .ToLocal(&obj) || |
| ret->Set(env->context(), i, obj).IsNothing()) { |
| return Nothing<int>(); |
| } |
| } |
| |
| /* Parse NAPTR records */ |
| if (!ParseNaptrReply(env, buf, len, ret, true).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS && status != ARES_ENODATA) { |
| return Just<int>(status); |
| } |
| |
| /* Parse SOA records */ |
| Local<Object> soa_record = Local<Object>(); |
| if (!ParseSoaReply(env, buf, len, &soa_record).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS && status != ARES_ENODATA) { |
| return Just<int>(status); |
| } |
| |
| if (!soa_record.IsEmpty()) { |
| if (ret->Set(env->context(), ret->Length(), soa_record).IsNothing()) { |
| return Just<int>(ARES_ENOMEM); |
| } |
| } |
| |
| /* Parse TLSA records */ |
| if (!ParseTlsaReply(env, buf, len, ret).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS && status != ARES_ENODATA) { |
| return Just<int>(status); |
| } |
| |
| /* Parse CAA records */ |
| if (!ParseCaaReply(env, buf, len, ret, true).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS && status != ARES_ENODATA) { |
| return Just<int>(status); |
| } |
| |
| wrap->CallOnComplete(ret); |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> ATraits::Parse(QueryAWrap* wrap, |
| const std::unique_ptr<ResponseData>& response) { |
| if (response->is_host) [[unlikely]] { |
| return Just<int>(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<Array> ret = Array::New(env->isolate()); |
| |
| int type = ns_t_a; |
| if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls) |
| .To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS) { |
| return Just<int>(status); |
| } |
| |
| Local<Array> ttls = AddrTTLToArray<ares_addrttl>(env, addrttls, naddrttls); |
| |
| wrap->CallOnComplete(ret, ttls); |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> AaaaTraits::Parse(QueryAaaaWrap* wrap, |
| const std::unique_ptr<ResponseData>& response) { |
| if (response->is_host) [[unlikely]] { |
| return Just<int>(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<Array> ret = Array::New(env->isolate()); |
| |
| int type = ns_t_aaaa; |
| if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls) |
| .To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS) { |
| return Just<int>(status); |
| } |
| |
| Local<Array> ttls = AddrTTLToArray<ares_addr6ttl>(env, addrttls, naddrttls); |
| |
| wrap->CallOnComplete(ret, ttls); |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> CaaTraits::Parse(QueryCaaWrap* wrap, |
| const std::unique_ptr<ResponseData>& response) { |
| if (response->is_host) [[unlikely]] { |
| return Just<int>(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<Array> ret = Array::New(env->isolate()); |
| int status; |
| if (!ParseCaaReply(env, buf, len, ret).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS) { |
| return Just<int>(status); |
| } |
| |
| wrap->CallOnComplete(ret); |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> CnameTraits::Parse(QueryCnameWrap* wrap, |
| const std::unique_ptr<ResponseData>& response) { |
| if (response->is_host) [[unlikely]] { |
| return Just<int>(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<Array> ret = Array::New(env->isolate()); |
| int type = ns_t_cname; |
| int status; |
| if (!ParseGeneralReply(env, buf, len, &type, ret).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS) { |
| return Just<int>(status); |
| } |
| |
| wrap->CallOnComplete(ret); |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> MxTraits::Parse(QueryMxWrap* wrap, |
| const std::unique_ptr<ResponseData>& response) { |
| if (response->is_host) [[unlikely]] { |
| return Just<int>(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<Array> mx_records = Array::New(env->isolate()); |
| int status; |
| if (!ParseMxReply(env, buf, len, mx_records).To(&status)) { |
| return Nothing<int>(); |
| } |
| |
| if (status != ARES_SUCCESS) { |
| return Just<int>(status); |
| } |
| |
| wrap->CallOnComplete(mx_records); |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> NsTraits::Parse(QueryNsWrap* wrap, |
| const std::unique_ptr<ResponseData>& response) { |
| if (response->is_host) [[unlikely]] { |
| return Just<int>(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<Array> names = Array::New(env->isolate()); |
| int status; |
| if (!ParseGeneralReply(env, buf, len, &type, names).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS) { |
| return Just<int>(status); |
| } |
| |
| wrap->CallOnComplete(names); |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> TlsaTraits::Parse(QueryTlsaWrap* wrap, |
| const std::unique_ptr<ResponseData>& response) { |
| if (response->is_host) [[unlikely]] { |
| return Just<int>(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<Array> tlsa_records = Array::New(env->isolate()); |
| int status; |
| if (!ParseTlsaReply(env, buf, len, tlsa_records).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS) { |
| return Just<int>(status); |
| } |
| |
| wrap->CallOnComplete(tlsa_records); |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> TxtTraits::Parse(QueryTxtWrap* wrap, |
| const std::unique_ptr<ResponseData>& response) { |
| if (response->is_host) [[unlikely]] { |
| return Just<int>(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<Array> txt_records = Array::New(env->isolate()); |
| int status; |
| if (!ParseTxtReply(env, buf, len, txt_records).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS) { |
| return Just<int>(status); |
| } |
| |
| wrap->CallOnComplete(txt_records); |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> SrvTraits::Parse(QuerySrvWrap* wrap, |
| const std::unique_ptr<ResponseData>& response) { |
| if (response->is_host) [[unlikely]] { |
| return Just<int>(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<Array> srv_records = Array::New(env->isolate()); |
| int status; |
| if (!ParseSrvReply(env, buf, len, srv_records).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS) return Just<int>(status); |
| |
| wrap->CallOnComplete(srv_records); |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> PtrTraits::Parse(QueryPtrWrap* wrap, |
| const std::unique_ptr<ResponseData>& response) { |
| if (response->is_host) [[unlikely]] { |
| return Just<int>(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<Array> aliases = Array::New(env->isolate()); |
| |
| int status; |
| if (!ParseGeneralReply(env, buf, len, &type, aliases).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS) { |
| return Just<int>(status); |
| } |
| |
| wrap->CallOnComplete(aliases); |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> NaptrTraits::Parse(QueryNaptrWrap* wrap, |
| const std::unique_ptr<ResponseData>& response) { |
| if (response->is_host) [[unlikely]] { |
| return Just<int>(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<Array> naptr_records = Array::New(env->isolate()); |
| int status; |
| if (!ParseNaptrReply(env, buf, len, naptr_records).To(&status)) { |
| return Nothing<int>(); |
| } |
| if (status != ARES_SUCCESS) { |
| return Just<int>(status); |
| } |
| |
| wrap->CallOnComplete(naptr_records); |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| Maybe<int> SoaTraits::Parse(QuerySoaWrap* wrap, |
| const std::unique_ptr<ResponseData>& response) { |
| if (response->is_host) [[unlikely]] { |
| return Just<int>(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<Value> 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<int>(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<Object> soa_record; |
| if (!NewDictionaryInstance(env->context(), tmpl, values) |
| .ToLocal(&soa_record)) { |
| return Nothing<int>(); |
| } |
| |
| wrap->CallOnComplete(soa_record); |
| return Just<int>(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<int> ReverseTraits::Parse(QueryReverseWrap* wrap, |
| const std::unique_ptr<ResponseData>& response) { |
| if (!response->is_host) [[unlikely]] { |
| return Just<int>(ARES_EBADRESP); |
| } |
| struct hostent* host = response->host.get(); |
| |
| Environment* env = wrap->env(); |
| HandleScope handle_scope(env->isolate()); |
| Context::Scope context_scope(env->context()); |
| Local<Array> names; |
| if (!HostentToNames(env, host).ToLocal(&names)) { |
| return Nothing<int>(); |
| } |
| wrap->CallOnComplete(names); |
| return Just<int>(ARES_SUCCESS); |
| } |
| |
| namespace { |
| template <class Wrap> |
| static void Query(const FunctionCallbackInfo<Value>& args) { |
| ChannelWrap* channel; |
| ASSIGN_OR_RETURN_UNWRAP(&channel, args.This()); |
| |
| CHECK_EQ(false, args.IsConstructCall()); |
| CHECK(args[0]->IsObject()); |
| CHECK(args[1]->IsString()); |
| |
| Local<Object> req_wrap_obj = args[0].As<Object>(); |
| Local<String> string = args[1].As<String>(); |
| auto wrap = std::make_unique<Wrap>(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<GetAddrInfoReqWrap> req_wrap{ |
| static_cast<GetAddrInfoReqWrap*>(req->data)}; |
| Environment* env = req_wrap->env(); |
| |
| HandleScope handle_scope(env->isolate()); |
| Context::Scope context_scope(env->context()); |
| |
| Local<Value> argv[] = { |
| Integer::New(env->isolate(), status), |
| Null(env->isolate()) |
| }; |
| |
| uint32_t n = 0; |
| const uint8_t order = req_wrap->order(); |
| |
| if (status == 0) { |
| Local<Array> results = Array::New(env->isolate()); |
| |
| auto add = [&](bool want_ipv4, bool want_ipv6) -> Maybe<void> { |
| 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<char*>( |
| &(reinterpret_cast<struct sockaddr_in*>(p->ai_addr)->sin_addr)); |
| } else if (want_ipv6 && p->ai_family == AF_INET6) { |
| addr = reinterpret_cast<char*>( |
| &(reinterpret_cast<struct sockaddr_in6*>(p->ai_addr)->sin6_addr)); |
| } else { |
| continue; |
| } |
| |
| char ip[INET6_ADDRSTRLEN]; |
| if (uv_inet_ntop(p->ai_family, addr, ip, sizeof(ip))) |
| continue; |
| |
| Local<String> s = OneByteString(env->isolate(), ip); |
| if (results->Set(env->context(), n, s).IsNothing()) |
| return Nothing<void>(); |
| 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<GetNameInfoReqWrap> req_wrap{ |
| static_cast<GetNameInfoReqWrap*>(req->data)}; |
| Environment* env = req_wrap->env(); |
| |
| HandleScope handle_scope(env->isolate()); |
| Context::Scope context_scope(env->context()); |
| |
| Local<Value> argv[] = { |
| Integer::New(env->isolate(), status), |
| Null(env->isolate()), |
| Null(env->isolate()) |
| }; |
| |
| if (status == 0) { |
| // Success |
| Local<String> js_hostname = OneByteString(env->isolate(), hostname); |
| Local<String> 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<Value>& 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<Value>& 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<Object> buffer; |
| if (node::Buffer::Copy( |
| isolate, reinterpret_cast<const char*>(dst), sizeof(dst)) |
| .ToLocal(&buffer)) { |
| args.GetReturnValue().Set(buffer); |
| } |
| } |
| |
| void GetAddrInfo(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| CHECK(args[0]->IsObject()); |
| CHECK(args[1]->IsString()); |
| CHECK(args[2]->IsInt32()); |
| CHECK(args[4]->IsUint32()); |
| Local<Object> req_wrap_obj = args[0].As<Object>(); |
| 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<Int32>()->Value(); |
| } |
| |
| int family; |
| |
| switch (args[2].As<Int32>()->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<Uint32> order = args[4].As<Uint32>(); |
| |
| auto req_wrap = |
| std::make_unique<GetAddrInfoReqWrap>(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<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| CHECK(args[0]->IsObject()); |
| CHECK(args[1]->IsString()); |
| CHECK(args[2]->IsUint32()); |
| Local<Object> req_wrap_obj = args[0].As<Object>(); |
| 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<sockaddr_in*>(&addr)) == 0 || |
| uv_ip6_addr(*ip, port, reinterpret_cast<sockaddr_in6*>(&addr)) == 0); |
| |
| auto req_wrap = std::make_unique<GetNameInfoReqWrap>(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<struct sockaddr*>(&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<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| ChannelWrap* channel; |
| ASSIGN_OR_RETURN_UNWRAP(&channel, args.This()); |
| |
| Local<Array> 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<const void*>(&cur->addr); |
| int err = uv_inet_ntop(cur->family, caddr, ip, sizeof(ip)); |
| CHECK_EQ(err, 0); |
| |
| Local<Value> 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<Value>& 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<Array> arr = args[0].As<Array>(); |
| |
| uint32_t len = arr->Length(); |
| |
| if (len == 0) { |
| int rv = ares_set_servers(channel->cares_channel(), nullptr); |
| return args.GetReturnValue().Set(rv); |
| } |
| |
| std::vector<ares_addr_port_node> servers(len); |
| ares_addr_port_node* last = nullptr; |
| |
| int err; |
| |
| for (uint32_t i = 0; i < len; i++) { |
| Local<Value> val; |
| if (!arr->Get(env->context(), i).ToLocal(&val)) return; |
| CHECK(val->IsArray()); |
| |
| Local<Array> elm = val.As<Array>(); |
| |
| Local<Value> familyValue; |
| Local<Value> ipValue; |
| Local<Value> 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<Value>& 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<Value>& 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<Value>& 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<Object> target, |
| Local<Value> unused, |
| Local<Context> 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<FunctionTemplate> aiw = |
| BaseObject::MakeLazilyInitializedJSTemplate(env); |
| aiw->Inherit(AsyncWrap::GetConstructorTemplate(env)); |
| SetConstructorFunction(context, target, "GetAddrInfoReqWrap", aiw); |
| |
| Local<FunctionTemplate> niw = |
| BaseObject::MakeLazilyInitializedJSTemplate(env); |
| niw->Inherit(AsyncWrap::GetConstructorTemplate(env)); |
| SetConstructorFunction(context, target, "GetNameInfoReqWrap", niw); |
| |
| Local<FunctionTemplate> qrw = |
| BaseObject::MakeLazilyInitializedJSTemplate(env); |
| qrw->Inherit(AsyncWrap::GetConstructorTemplate(env)); |
| SetConstructorFunction(context, target, "QueryReqWrap", qrw); |
| |
| Local<FunctionTemplate> 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##Name##Wrap>); |
| 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##Name##Wrap>); |
| 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) |