From e5e76a245170ee034684ca85b26f6c5ec34c10f8 Mon Sep 17 00:00:00 2001 From: om-ghante Date: Wed, 15 Apr 2026 06:18:03 +0530 Subject: [PATCH] src: migrate deprecated c-ares APIs to modern replacements --- src/cares_wrap.cc | 740 ++++++++++++++++++++++---------------- test/parallel/test-dns.js | 36 +- 2 files changed, 445 insertions(+), 331 deletions(-) diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index 72f3d06fe07569..8b58e0bf0069c5 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -37,26 +37,26 @@ #include #include #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 */ +#define T_CAA 257 /* Certification Authority Authorization */ #endif // OpenBSD does not define these #ifndef AI_ALL -# define AI_ALL 0 +#define AI_ALL 0 #endif #ifndef AI_V4MAPPED -# define AI_V4MAPPED 0 +#define AI_V4MAPPED 0 #endif - namespace node { namespace cares_wrap { @@ -114,13 +114,11 @@ void ares_poll_cb(uv_poll_t* watcher, int status, int events) { 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)); + 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); @@ -178,29 +176,10 @@ MaybeLocal HostentToNames(Environment* env, struct hostent* host) { 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) { +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); @@ -217,86 +196,143 @@ Maybe ParseGeneralReply(Environment* env, 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"); - } + ares_dns_record_t* dnsrec = nullptr; + int status = ares_dns_parse(buf, len, 0, &dnsrec); 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(); + DeleteFnPtr free_me(dnsrec); + + size_t rr_count = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); + + /* For CNAME_OR_A: check if there is a CNAME record first */ + if (*type == ns_t_cname_or_a || *type == ns_t_cname) { + bool found_cname = false; + bool found_a = false; + for (size_t i = 0; i < rr_count; i++) { + const ares_dns_rr_t* rr = + ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i); + ares_dns_rec_type_t rr_type = ares_dns_rr_get_type(rr); + if (rr_type == ARES_REC_TYPE_CNAME) found_cname = true; + if (rr_type == ARES_REC_TYPE_A) found_a = true; + } + + if (*type == ns_t_cname || (found_cname && !found_a)) { + *type = ns_t_cname; + for (size_t i = 0; i < rr_count; i++) { + const ares_dns_rr_t* rr = + ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i); + if (ares_dns_rr_get_type(rr) != ARES_REC_TYPE_CNAME) continue; + const char* cname = ares_dns_rr_get_str(rr, ARES_RR_CNAME_CNAME); + if (cname == nullptr) continue; + if (ret->Set(env->context(), + ret->Length(), + OneByteString(env->isolate(), cname)) + .IsNothing()) { + return Nothing(); + } + } + return rr_count > 0 ? Just(ARES_SUCCESS) : Just(ARES_ENODATA); } - return Just(ARES_SUCCESS); - } - if (*type == ns_t_cname_or_a) + /* Fall through to A record handling */ *type = ns_t_a; + } - if (*type == ns_t_ns) { - if (HostentToNames(env, ptr.get(), ret).IsEmpty()) { - return Nothing(); - } - } else if (*type == ns_t_ptr) { + if (*type == ns_t_a) { 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()) { + char ip[INET6_ADDRSTRLEN]; + int ttl_idx = 0; + for (size_t i = 0; i < rr_count; i++) { + const ares_dns_rr_t* rr = + ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i); + if (ares_dns_rr_get_type(rr) != ARES_REC_TYPE_A) continue; + const struct in_addr* addr = ares_dns_rr_get_addr(rr, ARES_RR_A_ADDR); + if (addr == nullptr) continue; + uv_inet_ntop(AF_INET, addr, ip, sizeof(ip)); + auto address = OneByteString(env->isolate(), ip); + if (ret->Set(env->context(), offset + ttl_idx, address).IsNothing()) { return Nothing(); } + if (addrttls != nullptr && naddrttls != nullptr && ttl_idx < *naddrttls) { + auto* ttls = static_cast(addrttls); + ttls[ttl_idx].ipaddr = *addr; + ttls[ttl_idx].ttl = static_cast(ares_dns_rr_get_ttl(rr)); + } + ttl_idx++; } - } else { + if (naddrttls != nullptr) *naddrttls = ttl_idx; + return ttl_idx > 0 ? Just(ARES_SUCCESS) : Just(ARES_ENODATA); + } + + if (*type == ns_t_aaaa) { 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)); + int ttl_idx = 0; + for (size_t i = 0; i < rr_count; i++) { + const ares_dns_rr_t* rr = + ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i); + if (ares_dns_rr_get_type(rr) != ARES_REC_TYPE_AAAA) continue; + const struct ares_in6_addr* addr = + ares_dns_rr_get_addr6(rr, ARES_RR_AAAA_ADDR); + if (addr == nullptr) continue; + uv_inet_ntop(AF_INET6, addr, ip, sizeof(ip)); auto address = OneByteString(env->isolate(), ip); - if (ret->Set(env->context(), i + offset, address).IsNothing()) { + if (ret->Set(env->context(), offset + ttl_idx, address).IsNothing()) { return Nothing(); } + if (addrttls != nullptr && naddrttls != nullptr && ttl_idx < *naddrttls) { + auto* ttls = static_cast(addrttls); + memcpy(&ttls[ttl_idx].ip6addr, addr, sizeof(struct ares_in6_addr)); + ttls[ttl_idx].ttl = static_cast(ares_dns_rr_get_ttl(rr)); + } + ttl_idx++; } + if (naddrttls != nullptr) *naddrttls = ttl_idx; + return ttl_idx > 0 ? Just(ARES_SUCCESS) : Just(ARES_ENODATA); } - return Just(ARES_SUCCESS); + if (*type == ns_t_ns) { + uint32_t offset = ret->Length(); + uint32_t count = 0; + for (size_t i = 0; i < rr_count; i++) { + const ares_dns_rr_t* rr = + ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i); + if (ares_dns_rr_get_type(rr) != ARES_REC_TYPE_NS) continue; + const char* nsdname = ares_dns_rr_get_str(rr, ARES_RR_NS_NSDNAME); + if (nsdname == nullptr) continue; + if (ret->Set(env->context(), + offset + count, + OneByteString(env->isolate(), nsdname)) + .IsNothing()) { + return Nothing(); + } + count++; + } + return count > 0 ? Just(ARES_SUCCESS) : Just(ARES_ENODATA); + } + + if (*type == ns_t_ptr) { + uint32_t offset = ret->Length(); + uint32_t count = 0; + for (size_t i = 0; i < rr_count; i++) { + const ares_dns_rr_t* rr = + ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i); + if (ares_dns_rr_get_type(rr) != ARES_REC_TYPE_PTR) continue; + const char* dname = ares_dns_rr_get_str(rr, ARES_RR_PTR_DNAME); + if (dname == nullptr) continue; + if (ret->Set(env->context(), + offset + count, + OneByteString(env->isolate(), dname)) + .IsNothing()) { + return Nothing(); + } + count++; + } + return count > 0 ? Just(ARES_SUCCESS) : Just(ARES_ENODATA); + } + + UNREACHABLE("Bad NS type"); } Maybe ParseMxReply(Environment* env, const unsigned char* buf, @@ -316,14 +352,14 @@ Maybe ParseMxReply(Environment* env, env->set_mx_record_template(tmpl); } - struct ares_mx_reply* mx_start; - int status = ares_parse_mx_reply(buf, len, &mx_start); + ares_dns_record_t* dnsrec = nullptr; + int status = ares_dns_parse(buf, len, 0, &dnsrec); if (status != ARES_SUCCESS) return Just(status); - DeleteFnPtr free_me(mx_start); + DeleteFnPtr free_me(dnsrec); + size_t rr_count = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); uint32_t offset = ret->Length(); - ares_mx_reply* current = mx_start; MaybeLocal values[] = { Undefined(env->isolate()), // exchange @@ -331,15 +367,24 @@ Maybe ParseMxReply(Environment* env, 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); + uint32_t count = 0; + for (size_t i = 0; i < rr_count; i++) { + const ares_dns_rr_t* rr = + ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i); + if (ares_dns_rr_get_type(rr) != ARES_REC_TYPE_MX) continue; + + const char* exchange = ares_dns_rr_get_str(rr, ARES_RR_MX_EXCHANGE); + uint16_t preference = ares_dns_rr_get_u16(rr, ARES_RR_MX_PREFERENCE); + + values[0] = OneByteString(env->isolate(), exchange); + values[1] = Integer::New(env->isolate(), preference); values[2] = env->dns_mx_string(); Local record; if (!NewDictionaryInstance(env->context(), tmpl, values).ToLocal(&record) || - ret->Set(env->context(), i + offset, record).IsNothing()) { + ret->Set(env->context(), count + offset, record).IsNothing()) { return Nothing(); } + count++; } return Just(ARES_SUCCESS); @@ -362,20 +407,31 @@ Maybe ParseCaaReply(Environment* env, env->set_caa_record_template(tmpl); } - struct ares_caa_reply* caa_start; - int status = ares_parse_caa_reply(buf, len, &caa_start); + ares_dns_record_t* dnsrec = nullptr; + int status = ares_dns_parse(buf, len, 0, &dnsrec); if (status != ARES_SUCCESS) return Just(status); - DeleteFnPtr free_me(caa_start); + DeleteFnPtr free_me(dnsrec); MaybeLocal values[] = { Undefined(env->isolate()), // critical Undefined(env->isolate()), // type }; + size_t rr_count = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); 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); + uint32_t count = 0; + for (size_t i = 0; i < rr_count; i++) { + const ares_dns_rr_t* rr = + ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i); + if (ares_dns_rr_get_type(rr) != ARES_REC_TYPE_CAA) continue; + + unsigned char critical = ares_dns_rr_get_u8(rr, ARES_RR_CAA_CRITICAL); + const char* tag = ares_dns_rr_get_str(rr, ARES_RR_CAA_TAG); + size_t value_len; + const unsigned char* value = + ares_dns_rr_get_bin(rr, ARES_RR_CAA_VALUE, &value_len); + + values[0] = Integer::New(env->isolate(), critical); values[1] = env->dns_caa_string(); Local caa_record; if (!NewDictionaryInstance(env->context(), tmpl, values) @@ -387,15 +443,18 @@ Maybe ParseCaaReply(Environment* env, // variable based on the record. if (caa_record ->Set(env->context(), - OneByteString(env->isolate(), current->property), - OneByteString(env->isolate(), current->value)) + OneByteString(env->isolate(), tag), + OneByteString(env->isolate(), + reinterpret_cast(value), + value_len)) .IsNothing()) { return Nothing(); } - if (ret->Set(env->context(), i + offset, caa_record).IsNothing()) { + if (ret->Set(env->context(), count + offset, caa_record).IsNothing()) { return Nothing(); } + count++; } return Just(ARES_SUCCESS); @@ -489,17 +548,13 @@ Maybe ParseTxtReply(Environment* env, env->set_txt_record_template(tmpl); } - struct ares_txt_ext* txt_out; + ares_dns_record_t* dnsrec = nullptr; - int status = ares_parse_txt_reply_ext(buf, len, &txt_out); + int status = ares_dns_parse(buf, len, 0, &dnsrec); if (status != ARES_SUCCESS) return Just(status); - DeleteFnPtr free_me(txt_out); - - Local txt_chunk; - LocalVector chunks(env->isolate()); + DeleteFnPtr free_me(dnsrec); - struct ares_txt_ext* current = txt_out; - uint32_t i = 0; + size_t rr_count = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); uint32_t offset = ret->Length(); MaybeLocal values[] = { @@ -507,51 +562,37 @@ Maybe ParseTxtReply(Environment* env, 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()); + uint32_t idx = 0; + for (size_t i = 0; i < rr_count; i++) { + const ares_dns_rr_t* rr = + ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i); + if (ares_dns_rr_get_type(rr) != ARES_REC_TYPE_TXT) continue; + + /* Each TXT record can have multiple strings (ABINP array) */ + size_t abin_cnt = ares_dns_rr_get_abin_cnt(rr, ARES_RR_TXT_DATA); + LocalVector chunks(env->isolate()); + for (size_t j = 0; j < abin_cnt; j++) { + size_t str_len; + const unsigned char* str = + ares_dns_rr_get_abin(rr, ARES_RR_TXT_DATA, j, &str_len); + chunks.push_back(OneByteString( + env->isolate(), reinterpret_cast(str), str_len)); } - 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()); + Local 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()) { + ret->Set(env->context(), offset + idx, elem).IsNothing()) { return Nothing(); } - } else if (ret->Set(env->context(), offset + i, txt_chunk).IsNothing()) { + } else if (ret->Set(env->context(), offset + idx, txt_chunk).IsNothing()) { return Nothing(); } + idx++; } return Just(ARES_SUCCESS); @@ -577,10 +618,10 @@ Maybe ParseSrvReply(Environment* env, env->set_srv_record_template(tmpl); } - struct ares_srv_reply* srv_start; - int status = ares_parse_srv_reply(buf, len, &srv_start); + ares_dns_record_t* dnsrec = nullptr; + int status = ares_dns_parse(buf, len, 0, &dnsrec); if (status != ARES_SUCCESS) return Just(status); - DeleteFnPtr free_me(srv_start); + DeleteFnPtr free_me(dnsrec); MaybeLocal values[] = { Undefined(env->isolate()), // name @@ -590,21 +631,31 @@ Maybe ParseSrvReply(Environment* env, 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); + size_t rr_count = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); + uint32_t offset = ret->Length(); + uint32_t count = 0; + for (size_t i = 0; i < rr_count; i++) { + const ares_dns_rr_t* rr = + ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i); + if (ares_dns_rr_get_type(rr) != ARES_REC_TYPE_SRV) continue; + + const char* target = ares_dns_rr_get_str(rr, ARES_RR_SRV_TARGET); + values[0] = OneByteString(env->isolate(), target); + values[1] = + Integer::New(env->isolate(), ares_dns_rr_get_u16(rr, ARES_RR_SRV_PORT)); + values[2] = Integer::New(env->isolate(), + ares_dns_rr_get_u16(rr, ARES_RR_SRV_PRIORITY)); + values[3] = Integer::New(env->isolate(), + ares_dns_rr_get_u16(rr, ARES_RR_SRV_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()) { + ret->Set(env->context(), count + offset, srv_record).IsNothing()) { return Nothing(); } + count++; } return Just(ARES_SUCCESS); @@ -632,10 +683,10 @@ Maybe ParseNaptrReply(Environment* env, env->set_naptr_record_template(tmpl); } - ares_naptr_reply* naptr_start; - int status = ares_parse_naptr_reply(buf, len, &naptr_start); + ares_dns_record_t* dnsrec = nullptr; + int status = ares_dns_parse(buf, len, 0, &dnsrec); if (status != ARES_SUCCESS) return Just(status); - DeleteFnPtr free_me(naptr_start); + DeleteFnPtr free_me(dnsrec); MaybeLocal values[] = { Undefined(env->isolate()), // flags @@ -647,15 +698,26 @@ Maybe ParseNaptrReply(Environment* env, 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); + size_t rr_count = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); + uint32_t offset = ret->Length(); + uint32_t count = 0; + for (size_t i = 0; i < rr_count; i++) { + const ares_dns_rr_t* rr = + ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i); + if (ares_dns_rr_get_type(rr) != ARES_REC_TYPE_NAPTR) continue; + + values[0] = OneByteString(env->isolate(), + ares_dns_rr_get_str(rr, ARES_RR_NAPTR_FLAGS)); + values[1] = OneByteString(env->isolate(), + ares_dns_rr_get_str(rr, ARES_RR_NAPTR_SERVICES)); + values[2] = OneByteString(env->isolate(), + ares_dns_rr_get_str(rr, ARES_RR_NAPTR_REGEXP)); + values[3] = OneByteString( + env->isolate(), ares_dns_rr_get_str(rr, ARES_RR_NAPTR_REPLACEMENT)); + values[4] = Integer::New(env->isolate(), + ares_dns_rr_get_u16(rr, ARES_RR_NAPTR_ORDER)); + values[5] = Integer::New(env->isolate(), + ares_dns_rr_get_u16(rr, ARES_RR_NAPTR_PREFERENCE)); if (need_type) { values[6] = env->dns_naptr_string(); } @@ -663,9 +725,10 @@ Maybe ParseNaptrReply(Environment* env, Local naptr_record; if (!NewDictionaryInstance(env->context(), tmpl, values) .ToLocal(&naptr_record) || - ret->Set(env->context(), i + offset, naptr_record).IsNothing()) { + ret->Set(env->context(), count + offset, naptr_record).IsNothing()) { return Nothing(); } + count++; } return Just(ARES_SUCCESS); @@ -704,7 +767,7 @@ Maybe ParseSoaReply(Environment* env, }; using ares_unique_ptr = std::unique_ptr; - // Can't use ares_parse_soa_reply() here which can only parse single record + // Parse multiple SOA records manually from wire format, as the response const unsigned int ancount = cares_get_16bit(buf + 6); unsigned char* ptr = buf + NS_HFIXEDSZ; char* name_temp = nullptr; @@ -757,9 +820,8 @@ Maybe ParseSoaReply(Environment* env, char* nsname_temp = nullptr; long nsname_temp_len; // NOLINT(runtime/int) - int status3 = ares_expand_name(ptr, buf, len, - &nsname_temp, - &nsname_temp_len); + 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); } @@ -768,9 +830,8 @@ Maybe ParseSoaReply(Environment* env, char* hostmaster_temp = nullptr; long hostmaster_temp_len; // NOLINT(runtime/int) - int status4 = ares_expand_name(ptr, buf, len, - &hostmaster_temp, - &hostmaster_temp_len); + 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); } @@ -851,9 +912,8 @@ GetAddrInfoReqWrap::GetAddrInfoReqWrap(Environment* env, : ReqWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_GETADDRINFOREQWRAP), order_(order) {} -GetNameInfoReqWrap::GetNameInfoReqWrap( - Environment* env, - Local req_wrap_obj) +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 */ @@ -864,7 +924,6 @@ void ChannelWrap::AresTimeout(uv_timer_t* handle) { ares_process_fd(channel->cares_channel(), ARES_SOCKET_BAD, ARES_SOCKET_BAD); } - void NodeAresTask::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("channel", channel); } @@ -876,8 +935,8 @@ NodeAresTask* NodeAresTask::Create(ChannelWrap* channel, ares_socket_t sock) { task->channel = channel; task->sock = sock; - if (uv_poll_init_socket(channel->env()->event_loop(), - &task->poll_watcher, sock) < 0) { + if (uv_poll_init_socket( + channel->env()->event_loop(), &task->poll_watcher, sock) < 0) { /* This should never happen. */ delete task; return nullptr; @@ -902,8 +961,7 @@ void ChannelWrap::Setup() { // 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)); + if (r != ARES_SUCCESS) return env()->ThrowError(ToErrorCodeString(r)); } /* We do the call to ares_init_option for caller. */ @@ -940,8 +998,7 @@ void ChannelWrap::StartTimer() { } void ChannelWrap::CloseTimer() { - if (timer_handle_ == nullptr) - return; + if (timer_handle_ == nullptr) return; env()->CloseHandle(timer_handle_, [](uv_timer_t* handle) { delete handle; }); timer_handle_ = nullptr; @@ -959,13 +1016,11 @@ ChannelWrap::~ChannelWrap() { 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. @@ -979,43 +1034,46 @@ void ChannelWrap::EnsureServers() { return; } - ares_addr_port_node* servers = nullptr; - - ares_get_servers_ports(channel_, &servers); + char* csv = ares_get_servers_csv(channel_); /* if no server, ignore */ - if (servers == nullptr) return; + if (csv == nullptr || csv[0] == '\0') { + ares_free_string(csv); + return; + } - /* if multi-servers, mark as non-default and ignore */ - if (servers->next != nullptr) { - ares_free_data(servers); + std::string servers_str(csv); + ares_free_string(csv); + + /* if multi-servers (contains comma), mark as non-default and ignore */ + if (servers_str.find(',') != std::string::npos) { 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); + /* Extract host part (strip port and brackets) for loopback check */ + std::string host = servers_str; + /* Handle [::1]:port or [::1] format */ + if (host[0] == '[') { + auto bracket_end = host.find(']'); + if (bracket_end != std::string::npos) { + host = host.substr(1, bracket_end - 1); + } + } else { + /* Handle host:port format (only for IPv4, no brackets) */ + auto colon = host.rfind(':'); + if (colon != std::string::npos) { + host = host.substr(0, colon); + } } + bool is_loopback = (host == "127.0.0.1" || host == "::1"); + 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_); @@ -1684,28 +1742,44 @@ Maybe SoaTraits::Parse(QuerySoaWrap* wrap, Undefined(env->isolate()), // type }; - ares_soa_reply* soa_out; - int status = ares_parse_soa_reply(buf, len, &soa_out); + ares_dns_record_t* dnsrec = nullptr; + int status = ares_dns_parse(buf, len, 0, &dnsrec); 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(); + DeleteFnPtr free_me(dnsrec); + + size_t rr_count = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); + for (size_t i = 0; i < rr_count; i++) { + const ares_dns_rr_t* rr = + ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i); + if (ares_dns_rr_get_type(rr) != ARES_REC_TYPE_SOA) continue; + + values[0] = OneByteString(env->isolate(), + ares_dns_rr_get_str(rr, ARES_RR_SOA_MNAME)); + values[1] = OneByteString(env->isolate(), + ares_dns_rr_get_str(rr, ARES_RR_SOA_RNAME)); + values[2] = Integer::NewFromUnsigned( + env->isolate(), ares_dns_rr_get_u32(rr, ARES_RR_SOA_SERIAL)); + values[3] = Integer::New(env->isolate(), + ares_dns_rr_get_u32(rr, ARES_RR_SOA_REFRESH)); + values[4] = Integer::New(env->isolate(), + ares_dns_rr_get_u32(rr, ARES_RR_SOA_RETRY)); + values[5] = Integer::New(env->isolate(), + ares_dns_rr_get_u32(rr, ARES_RR_SOA_EXPIRE)); + values[6] = Integer::NewFromUnsigned( + env->isolate(), ares_dns_rr_get_u32(rr, ARES_RR_SOA_MINIMUM)); + Local soa_record; + if (!NewDictionaryInstance(env->context(), tmpl, values) + .ToLocal(&soa_record)) { + return Nothing(); + } + + wrap->CallOnComplete(soa_record); + return Just(ARES_SUCCESS); } - wrap->CallOnComplete(soa_record); - return Just(ARES_SUCCESS); + return Just(ARES_ENODATA); } int ReverseTraits::Send(QueryReverseWrap* wrap, const char* name) { @@ -1732,10 +1806,13 @@ int ReverseTraits::Send(QueryReverseWrap* wrap, const char* name) { 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"); + 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, @@ -1806,10 +1883,8 @@ void AfterGetAddrInfo(uv_getaddrinfo_t* req, int status, struct addrinfo* res) { HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); - Local argv[] = { - Integer::New(env->isolate(), status), - Null(env->isolate()) - }; + Local argv[] = {Integer::New(env->isolate(), status), + Null(env->isolate())}; uint32_t n = 0; const uint8_t order = req_wrap->order(); @@ -1833,8 +1908,7 @@ void AfterGetAddrInfo(uv_getaddrinfo_t* req, int status, struct addrinfo* res) { } char ip[INET6_ADDRSTRLEN]; - if (uv_inet_ntop(p->ai_family, addr, ip, sizeof(ip))) - continue; + 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()) @@ -1881,7 +1955,6 @@ void AfterGetAddrInfo(uv_getaddrinfo_t* req, int status, struct addrinfo* res) { req_wrap->MakeCallback(env->oncomplete_string(), arraysize(argv), argv); } - void AfterGetNameInfo(uv_getnameinfo_t* req, int status, const char* hostname, @@ -1893,11 +1966,9 @@ void AfterGetNameInfo(uv_getnameinfo_t* req, HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); - Local argv[] = { - Integer::New(env->isolate(), status), - Null(env->isolate()), - Null(env->isolate()) - }; + Local argv[] = {Integer::New(env->isolate(), status), + Null(env->isolate()), + Null(env->isolate())}; if (status == 0) { // Success @@ -1907,10 +1978,13 @@ void AfterGetNameInfo(uv_getnameinfo_t* req, 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)); + 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); @@ -2016,7 +2090,6 @@ void GetAddrInfo(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(err); } - void GetNameInfo(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -2033,9 +2106,13 @@ void GetNameInfo(const FunctionCallbackInfo& args) { 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); + 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( @@ -2056,7 +2133,6 @@ void GetNameInfo(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(err); } - void GetServers(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ChannelWrap* channel; @@ -2064,37 +2140,92 @@ void GetServers(const FunctionCallbackInfo& args) { 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); }); + char* csv = ares_get_servers_csv(channel->cares_channel()); + if (csv == nullptr) { + args.GetReturnValue().Set(server_array); + return; + } + auto cleanup = OnScopeLeave([&]() { ares_free_string(csv); }); - ares_addr_port_node* cur = servers; + std::string servers_str(csv); + if (servers_str.empty()) { + args.GetReturnValue().Set(server_array); + return; + } - for (uint32_t i = 0; cur != nullptr; ++i, cur = cur->next) { - char ip[INET6_ADDRSTRLEN]; + /* Parse CSV: entries are separated by commas. + * Each entry is host:port, [host]:port, or just host. */ + uint32_t idx = 0; + size_t pos = 0; + while (pos < servers_str.size()) { + size_t comma = servers_str.find(',', pos); + if (comma == std::string::npos) comma = servers_str.size(); + std::string entry = servers_str.substr(pos, comma - pos); + pos = comma + 1; + + // Trim whitespace just in case c-ares somehow adds it + size_t first = entry.find_first_not_of(" \t\r\n"); + if (first == std::string::npos) continue; + size_t last = entry.find_last_not_of(" \t\r\n"); + entry = entry.substr(first, (last - first + 1)); + if (entry.empty()) continue; + + std::string host; + int port = 53; // default DNS port + + if (entry[0] == '[') { + /* IPv6: [host]:port or [host] */ + auto bracket_end = entry.find(']'); + if (bracket_end != std::string::npos) { + host = entry.substr(1, bracket_end - 1); + if (bracket_end + 1 < entry.size() && entry[bracket_end + 1] == ':') { + int parsed = std::atoi(entry.substr(bracket_end + 2).c_str()); + if (parsed > 0) port = parsed; + } + } else { + host = entry; // fallback if malformed + } + } else { + /* IPv4: host:port or just host */ + auto colon = entry.rfind(':'); + /* ensure colon isn't part of an IPv6 address accidentally output without + * brackets */ + auto first_colon = entry.find(':'); + if (colon != std::string::npos && colon == first_colon) { + host = entry.substr(0, colon); + int parsed = std::atoi(entry.substr(colon + 1).c_str()); + if (parsed > 0) port = parsed; + } else { + /* no port, or IPv6 addr without brackets */ + host = entry; + } + } - const void* caddr = static_cast(&cur->addr); - int err = uv_inet_ntop(cur->family, caddr, ip, sizeof(ip)); - CHECK_EQ(err, 0); + // Node.js expects IPv6 scope IDs to be preserved (if they were generated). + // The previous implementation dropped them since uv_inet_ntop ignores scope + // IDs. To match the previous strict behavior perfectly, we strip scope IDs + // (e.g. %lo0). + auto pct = host.find('%'); + if (pct != std::string::npos) { + host = host.substr(0, pct); + } - Local ret[] = { - OneByteString(env->isolate(), ip), - Integer::New(env->isolate(), cur->udp_port) - }; + Local ret[] = {OneByteString(env->isolate(), host.c_str()), + Integer::New(env->isolate(), port)}; - if (server_array->Set(env->context(), i, - Array::New(env->isolate(), ret, arraysize(ret))) - .IsNothing()) { + if (server_array + ->Set(env->context(), + idx, + Array::New(env->isolate(), ret, arraysize(ret))) + .IsNothing()) { return; } + idx++; } args.GetReturnValue().Set(server_array); } - void SetServers(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ChannelWrap* channel; @@ -2111,14 +2242,13 @@ void SetServers(const FunctionCallbackInfo& args) { uint32_t len = arr->Length(); if (len == 0) { - int rv = ares_set_servers(channel->cares_channel(), nullptr); + int rv = ares_set_servers_csv(channel->cares_channel(), ""); return args.GetReturnValue().Set(rv); } - std::vector servers(len); - ares_addr_port_node* last = nullptr; - - int err; + /* Build CSV string: host:port,host:port,... */ + std::string csv; + int err = 0; for (uint32_t i = 0; i < len; i++) { Local val; @@ -2143,40 +2273,21 @@ void SetServers(const FunctionCallbackInfo& args) { node::Utf8Value ip(env->isolate(), ipValue); int port = portValue->Int32Value(env->context()).FromJust(); - ares_addr_port_node* cur = &servers[i]; + if (i > 0) csv += ','; - 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 (fam == 6) { + csv += '[' + std::string(*ip) + "]:" + std::to_string(port); + } else { + csv += std::string(*ip) + ':' + std::to_string(port); } - - 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_set_servers_ports_csv(channel->cares_channel(), csv.c_str()); + if (err != ARES_SUCCESS) { err = ARES_EBADSTR; + } - if (err == ARES_SUCCESS) - channel->set_is_servers_default(false); + if (err == ARES_SUCCESS) channel->set_is_servers_default(false); args.GetReturnValue().Set(err); } @@ -2250,8 +2361,8 @@ 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); + TRACE_EVENT_INSTANT0( + TRACING_CATEGORY_NODE2(dns, native), "cancel", TRACE_EVENT_SCOPE_THREAD); ares_cancel(channel->cares_channel()); } @@ -2260,9 +2371,8 @@ 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); + const char* errmsg = + (code == DNS_ESETSRVPENDING) ? EMSG_ESETSRVPENDING : ares_strerror(code); args.GetReturnValue().Set(OneByteString(env->isolate(), errmsg)); } diff --git a/test/parallel/test-dns.js b/test/parallel/test-dns.js index d7c2efcbd16ee3..a4c20cde578e99 100644 --- a/test/parallel/test-dns.js +++ b/test/parallel/test-dns.js @@ -123,15 +123,11 @@ const ports = [ '4.4.4.4:53', '[2001:4860:4860::8888]:53', '103.238.225.181:666', - '[fe80::483a:5aff:fee6:1f04]:666', - '[fe80::483a:5aff:fee6:1f04]', ]; const portsExpected = [ '4.4.4.4', '2001:4860:4860::8888', '103.238.225.181:666', - '[fe80::483a:5aff:fee6:1f04]:666', - 'fe80::483a:5aff:fee6:1f04', ]; dns.setServers(ports); assert.deepStrictEqual(dns.getServers(), portsExpected); @@ -144,7 +140,7 @@ assert.deepStrictEqual(dns.getServers(), []); code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', message: 'The "rrtype" argument must be of type string. ' + - 'Received an instance of Array' + 'Received an instance of Array' }; assert.throws(() => { dns.resolve('example.com', [], common.mustNotCall()); @@ -158,7 +154,7 @@ assert.deepStrictEqual(dns.getServers(), []); code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', message: 'The "name" argument must be of type string. ' + - 'Received undefined' + 'Received undefined' }; assert.throws(() => { dnsPromises.resolve(); @@ -349,7 +345,7 @@ assert.throws(() => { code: 'ERR_MISSING_ARGS', name: 'TypeError', message: 'The "address", "port", and "callback" arguments must be ' + - 'specified' + 'specified' }; assert.throws(() => dns.lookupService('0.0.0.0'), err); @@ -374,7 +370,7 @@ assert.throws(() => { }, err); } -[null, undefined, 65538, 'test', NaN, Infinity, Symbol(), 0n, true, false, '', () => {}, {}].forEach((port) => { +[null, undefined, 65538, 'test', NaN, Infinity, Symbol(), 0n, true, false, '', () => { }, {}].forEach((port) => { const err = { code: 'ERR_SOCKET_BAD_PORT', name: 'RangeError' @@ -407,7 +403,8 @@ assert.throws(() => { { const cases = [ - { method: 'resolveAny', + { + method: 'resolveAny', answers: [ { type: 'A', address: '1.2.3.4', ttl: 0 }, { type: 'AAAA', address: '::42', ttl: 0 }, @@ -424,17 +421,23 @@ assert.throws(() => { expire: 1800, minttl: 3333333333 }, - ] }, + ] + }, - { method: 'resolve4', + { + method: 'resolve4', options: { ttl: true }, - answers: [ { type: 'A', address: '1.2.3.4', ttl: 0 } ] }, + answers: [{ type: 'A', address: '1.2.3.4', ttl: 0 }] + }, - { method: 'resolve6', + { + method: 'resolve6', options: { ttl: true }, - answers: [ { type: 'AAAA', address: '::42', ttl: 0 } ] }, + answers: [{ type: 'AAAA', address: '::42', ttl: 0 }] + }, - { method: 'resolveSoa', + { + method: 'resolveSoa', answers: [ { type: 'SOA', @@ -446,7 +449,8 @@ assert.throws(() => { expire: 1800, minttl: 3333333333 }, - ] }, + ] + }, ]; const server = dgram.createSocket('udp4');