diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index 72f3d06fe07569..1c576cb35f7d0c 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -35,10 +35,14 @@ #include "v8.h" #include +#include #include #include -#include +#include +#include #include +#include +#include #ifndef T_TLSA #define T_TLSA 52 /* TLSA certificate association */ @@ -178,24 +182,6 @@ 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, @@ -209,6 +195,60 @@ Local AddrTTLToArray( return Array::New(env->isolate(), ttls.out(), naddrttls); } +// Parse the CSV produced by ares_get_servers_csv() back into (ip, port) +// pairs. Each entry is "ipv4:port" or "[ipv6]:port"; entries whose UDP and +// TCP ports differ are emitted in the "dns://host:port?tcpport=..." URI form. +// A trailing "%interface" is ignored. +std::vector> ParseServersCsv(const char* csv) { + std::vector> servers; + if (csv == nullptr) return servers; + + std::string_view all(csv); + size_t pos = 0; + while (pos <= all.size()) { + size_t comma = all.find(',', pos); + std::string_view entry = all.substr( + pos, + comma == std::string_view::npos ? std::string_view::npos : comma - pos); + pos = comma == std::string_view::npos ? all.size() + 1 : comma + 1; + + // Drop a URI scheme prefix (only present when udp/tcp ports differ), + // a URI query string, and/or a trailing interface name. + size_t scheme = entry.find("://"); + if (scheme != std::string_view::npos) entry = entry.substr(scheme + 3); + size_t query = entry.find('?'); + if (query != std::string_view::npos) entry = entry.substr(0, query); + size_t iface = entry.find('%'); + if (iface != std::string_view::npos) entry = entry.substr(0, iface); + if (entry.empty()) continue; + + std::string host; + int port = 53; + std::string_view port_str; + if (entry.front() == '[') { + size_t close = entry.find(']'); + if (close == std::string_view::npos) continue; + host = std::string(entry.substr(1, close - 1)); + size_t colon = entry.find(':', close); + if (colon != std::string_view::npos) port_str = entry.substr(colon + 1); + } else { + size_t colon = entry.rfind(':'); + if (colon != std::string_view::npos) { + host = std::string(entry.substr(0, colon)); + port_str = entry.substr(colon + 1); + } else { + host = std::string(entry); + } + } + if (!port_str.empty()) { + std::string s(port_str); + port = static_cast(strtol(s.c_str(), nullptr, 10)); + } + servers.emplace_back(std::move(host), port); + } + return servers; +} + Maybe ParseGeneralReply(Environment* env, const unsigned char* buf, int len, @@ -217,87 +257,134 @@ Maybe ParseGeneralReply(Environment* env, void* addrttls = nullptr, int* naddrttls = nullptr) { HandleScope handle_scope(env->isolate()); - hostent* host; - int status; + ares_dns_record_t* dnsrec = nullptr; + int status = ares_dns_parse(buf, len, 0, &dnsrec); + if (status != ARES_SUCCESS) { + ares_dns_record_destroy(dnsrec); + return Just(status); + } + DeleteFnPtr free_me(dnsrec); + + const size_t rr_count = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); + + /* If it's `CNAME_OR_A`, a response that carries a CNAME record is reported + * as a CNAME; otherwise it is reported as an A record. */ + if (*type == ns_t_cname_or_a) { + bool has_cname = false; + 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_CNAME) { + has_cname = true; + break; + } + } + *type = has_cname ? ns_t_cname : ns_t_a; + } + + const uint32_t offset = ret->Length(); + uint32_t count = 0; + + auto append_name = [&](ares_dns_rec_type_t rec_type, + ares_dns_rr_key_t key) -> Maybe { + 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) != rec_type) continue; + const char* name = ares_dns_rr_get_str(rr, key); + if (name == nullptr) continue; + if (ret->Set(env->context(), + offset + count++, + OneByteString(env->isolate(), name)) + .IsNothing()) { + return Nothing(); + } + } + return Just(true); + }; + 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); + // A cname lookup always returns a single record in practice but we + // follow the common API here and return every CNAME target. + if (append_name(ARES_REC_TYPE_CNAME, ARES_RR_CNAME_CNAME).IsNothing()) { + return Nothing(); + } break; case ns_t_ns: - status = ares_parse_ns_reply(buf, len, &host); + if (append_name(ARES_REC_TYPE_NS, ARES_RR_NS_NSDNAME).IsNothing()) { + return Nothing(); + } break; case ns_t_ptr: - status = ares_parse_ptr_reply(buf, len, nullptr, 0, AF_INET, &host); - break; - default: - UNREACHABLE("Bad NS type"); - } - - if (status != ARES_SUCCESS) return Just(status); - - CHECK_NOT_NULL(host); - HostEntPointer ptr(host); - - /* If it's `CNAME`, return the CNAME value; - * And if it's `CNAME_OR_A` and it has value in `h_name` and `h_aliases[0]`, - * we consider it's a CNAME record, otherwise we consider it's an A record. */ - if ((*type == ns_t_cname_or_a && ptr->h_name && ptr->h_aliases[0]) || - *type == ns_t_cname) { - // A cname lookup always returns a single record but we follow the - // common API here. - *type = ns_t_cname; - if (ret->Set(env->context(), - ret->Length(), - OneByteString(env->isolate(), ptr->h_name)) - .IsNothing()) { - return Nothing(); - } - return Just(ARES_SUCCESS); - } - - if (*type == ns_t_cname_or_a) - *type = ns_t_a; - - if (*type == ns_t_ns) { - if (HostentToNames(env, ptr.get(), ret).IsEmpty()) { - return Nothing(); - } - } else if (*type == ns_t_ptr) { - uint32_t offset = ret->Length(); - for (uint32_t i = 0; ptr->h_aliases[i] != nullptr; i++) { - auto alias = OneByteString(env->isolate(), ptr->h_aliases[i]); - if (ret->Set(env->context(), i + offset, alias).IsNothing()) { + if (append_name(ARES_REC_TYPE_PTR, ARES_RR_PTR_DNAME).IsNothing()) { return Nothing(); } + break; + case ns_t_a: { + auto* ttls = static_cast(addrttls); + const int max = naddrttls != nullptr ? *naddrttls : 0; + char ip[INET6_ADDRSTRLEN]; + 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_A) continue; + if (ttls != nullptr && static_cast(count) >= max) break; + 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)); + if (ttls != nullptr) { + ttls[count].ipaddr = *addr; + ttls[count].ttl = ares_dns_rr_get_ttl(rr); + } + if (ret->Set(env->context(), + offset + count++, + OneByteString(env->isolate(), ip)) + .IsNothing()) { + return Nothing(); + } + } + if (naddrttls != nullptr) *naddrttls = count; + break; } - } 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(); + case ns_t_aaaa: { + auto* ttls = static_cast(addrttls); + const int max = naddrttls != nullptr ? *naddrttls : 0; + char ip[INET6_ADDRSTRLEN]; + 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_AAAA) continue; + if (ttls != nullptr && static_cast(count) >= max) break; + 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)); + if (ttls != nullptr) { + ttls[count].ip6addr = *addr; + ttls[count].ttl = ares_dns_rr_get_ttl(rr); + } + if (ret->Set(env->context(), + offset + count++, + OneByteString(env->isolate(), ip)) + .IsNothing()) { + return Nothing(); + } } + if (naddrttls != nullptr) *naddrttls = count; + break; } + default: + UNREACHABLE("Bad NS type"); } + // Preserve the behavior of the previous ares_parse_*_reply() helpers, which + // reported an empty answer section as ARES_ENODATA. + if (count == 0) return Just(ARES_ENODATA); return Just(ARES_SUCCESS); } + Maybe ParseMxReply(Environment* env, const unsigned char* buf, int len, @@ -316,14 +403,17 @@ 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); - if (status != ARES_SUCCESS) return Just(status); - - DeleteFnPtr free_me(mx_start); + ares_dns_record_t* dnsrec = nullptr; + int status = ares_dns_parse(buf, len, 0, &dnsrec); + if (status != ARES_SUCCESS) { + ares_dns_record_destroy(dnsrec); + return Just(status); + } + DeleteFnPtr free_me(dnsrec); + const size_t rr_count = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); uint32_t offset = ret->Length(); - ares_mx_reply* current = mx_start; + uint32_t count = 0; MaybeLocal values[] = { Undefined(env->isolate()), // exchange @@ -331,17 +421,25 @@ 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); + 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_MX) continue; + + const char* exchange = ares_dns_rr_get_str(rr, ARES_RR_MX_EXCHANGE); + unsigned short priority = // NOLINT(runtime/int) + ares_dns_rr_get_u16(rr, ARES_RR_MX_PREFERENCE); + values[0] = OneByteString(env->isolate(), exchange); + values[1] = Integer::New(env->isolate(), priority); values[2] = env->dns_mx_string(); Local record; if (!NewDictionaryInstance(env->context(), tmpl, values).ToLocal(&record) || - ret->Set(env->context(), i + offset, record).IsNothing()) { + ret->Set(env->context(), offset + count++, record).IsNothing()) { return Nothing(); } } + if (count == 0) return Just(ARES_ENODATA); return Just(ARES_SUCCESS); } @@ -362,20 +460,29 @@ 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); - if (status != ARES_SUCCESS) return Just(status); - DeleteFnPtr free_me(caa_start); + ares_dns_record_t* dnsrec = nullptr; + int status = ares_dns_parse(buf, len, 0, &dnsrec); + if (status != ARES_SUCCESS) { + ares_dns_record_destroy(dnsrec); + return Just(status); + } + DeleteFnPtr free_me(dnsrec); MaybeLocal values[] = { Undefined(env->isolate()), // critical Undefined(env->isolate()), // type }; + const 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(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); + values[0] = Integer::New(env->isolate(), critical); values[1] = env->dns_caa_string(); Local caa_record; if (!NewDictionaryInstance(env->context(), tmpl, values) @@ -383,21 +490,27 @@ Maybe ParseCaaReply(Environment* env, return Nothing(); } + const char* tag = ares_dns_rr_get_str(rr, ARES_RR_CAA_TAG); + size_t value_len = 0; + const unsigned char* value = + ares_dns_rr_get_bin(rr, ARES_RR_CAA_VALUE, &value_len); + // 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)) + OneByteString(env->isolate(), tag), + OneByteString(env->isolate(), value, value_len)) .IsNothing()) { return Nothing(); } - if (ret->Set(env->context(), i + offset, caa_record).IsNothing()) { + if (ret->Set(env->context(), offset + count++, caa_record).IsNothing()) { return Nothing(); } } + if (count == 0) return Just(ARES_ENODATA); return Just(ARES_SUCCESS); } @@ -489,71 +602,55 @@ Maybe ParseTxtReply(Environment* env, env->set_txt_record_template(tmpl); } - struct ares_txt_ext* txt_out; - - int status = ares_parse_txt_reply_ext(buf, len, &txt_out); - if (status != ARES_SUCCESS) return Just(status); - DeleteFnPtr free_me(txt_out); - - Local txt_chunk; - LocalVector chunks(env->isolate()); + ares_dns_record_t* dnsrec = nullptr; + int status = ares_dns_parse(buf, len, 0, &dnsrec); + if (status != ARES_SUCCESS) { + ares_dns_record_destroy(dnsrec); + return Just(status); + } + DeleteFnPtr free_me(dnsrec); - struct ares_txt_ext* current = txt_out; - uint32_t i = 0; + const size_t rr_count = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); uint32_t offset = ret->Length(); + uint32_t count = 0; MaybeLocal values[] = { Undefined(env->isolate()), // entries Undefined(env->isolate()), // type }; - for (; current != nullptr; current = current->next) { - Local txt = - OneByteString(env->isolate(), current->txt, current->length); - - // New record found - write out the current chunk - if (current->record_start) { - if (!chunks.empty()) { - auto txt_chunk = - Array::New(env->isolate(), chunks.data(), chunks.size()); - chunks.clear(); - if (need_type) { - values[0] = txt_chunk; - values[1] = env->dns_txt_string(); - Local elem; - if (!NewDictionaryInstance(env->context(), tmpl, values) - .ToLocal(&elem) || - ret->Set(env->context(), offset + i++, elem).IsNothing()) { - return Nothing(); - } - } else if (ret->Set(env->context(), offset + i++, txt_chunk) - .IsNothing()) { - return Nothing(); - } - } - - txt_chunk = Array::New(env->isolate()); + 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_TXT) continue; + + // Each TXT record is a chunk consisting of one or more character-strings. + LocalVector chunks(env->isolate()); + size_t str_count = ares_dns_rr_get_abin_cnt(rr, ARES_RR_TXT_DATA); + for (size_t j = 0; j < str_count; j++) { + size_t str_len = 0; + const unsigned char* str = + ares_dns_rr_get_abin(rr, ARES_RR_TXT_DATA, j, &str_len); + chunks.push_back(OneByteString(env->isolate(), 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 + count++, elem).IsNothing()) { return Nothing(); } - } else if (ret->Set(env->context(), offset + i, txt_chunk).IsNothing()) { + } else if (ret->Set(env->context(), offset + count++, txt_chunk) + .IsNothing()) { return Nothing(); } } + if (count == 0) return Just(ARES_ENODATA); return Just(ARES_SUCCESS); } @@ -577,10 +674,13 @@ 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); - if (status != ARES_SUCCESS) return Just(status); - DeleteFnPtr free_me(srv_start); + ares_dns_record_t* dnsrec = nullptr; + int status = ares_dns_parse(buf, len, 0, &dnsrec); + if (status != ARES_SUCCESS) { + ares_dns_record_destroy(dnsrec); + return Just(status); + } + DeleteFnPtr free_me(dnsrec); MaybeLocal values[] = { Undefined(env->isolate()), // name @@ -590,23 +690,33 @@ 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); + const 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(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(), offset + count++, srv_record).IsNothing()) { return Nothing(); } } + if (count == 0) return Just(ARES_ENODATA); return Just(ARES_SUCCESS); } @@ -632,10 +742,13 @@ 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); - if (status != ARES_SUCCESS) return Just(status); - DeleteFnPtr free_me(naptr_start); + ares_dns_record_t* dnsrec = nullptr; + int status = ares_dns_parse(buf, len, 0, &dnsrec); + if (status != ARES_SUCCESS) { + ares_dns_record_destroy(dnsrec); + return Just(status); + } + DeleteFnPtr free_me(dnsrec); MaybeLocal values[] = { Undefined(env->isolate()), // flags @@ -647,15 +760,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); + const 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(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,11 +787,12 @@ 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(), offset + count++, naptr_record).IsNothing()) { return Nothing(); } } + if (count == 0) return Just(ARES_ENODATA); return Just(ARES_SUCCESS); } @@ -979,16 +1104,15 @@ void ChannelWrap::EnsureServers() { return; } - ares_addr_port_node* servers = nullptr; - - ares_get_servers_ports(channel_, &servers); + char* csv = ares_get_servers_csv(channel_); + auto cleanup = OnScopeLeave([&]() { ares_free_string(csv); }); + std::vector> servers = ParseServersCsv(csv); /* if no server, ignore */ - if (servers == nullptr) return; + if (servers.empty()) return; /* if multi-servers, mark as non-default and ignore */ - if (servers->next != nullptr) { - ares_free_data(servers); + if (servers.size() > 1) { is_servers_default_ = false; return; } @@ -996,26 +1120,12 @@ void ChannelWrap::EnsureServers() { /* 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); + const std::string& host = servers[0].first; + if (host != "127.0.0.1" && host != "::1") { is_servers_default_ = false; return; } - ares_free_data(servers); - servers = nullptr; - /* destroy channel and reset channel */ ares_destroy(channel_); @@ -1684,20 +1794,41 @@ Maybe SoaTraits::Parse(QuerySoaWrap* wrap, Undefined(env->isolate()), // type }; - ares_soa_reply* soa_out; - int status = ares_parse_soa_reply(buf, len, &soa_out); - - if (status != ARES_SUCCESS) return Just(status); + ares_dns_record_t* dnsrec = nullptr; + int status = ares_dns_parse(buf, len, 0, &dnsrec); + if (status != ARES_SUCCESS) { + ares_dns_record_destroy(dnsrec); + return Just(status); + } + DeleteFnPtr free_me(dnsrec); - auto cleanup = OnScopeLeave([&]() { ares_free_data(soa_out); }); + const size_t rr_count = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); + const ares_dns_rr_t* soa = nullptr; + 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_SOA) { + soa = rr; + break; + } + } - 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); + if (soa == nullptr) return Just(ARES_ENODATA); + + const char* nsname = ares_dns_rr_get_str(soa, ARES_RR_SOA_MNAME); + const char* hostmaster = ares_dns_rr_get_str(soa, ARES_RR_SOA_RNAME); + values[0] = OneByteString(env->isolate(), nsname); + values[1] = OneByteString(env->isolate(), hostmaster); + values[2] = Integer::NewFromUnsigned( + env->isolate(), ares_dns_rr_get_u32(soa, ARES_RR_SOA_SERIAL)); + values[3] = Integer::New(env->isolate(), + ares_dns_rr_get_u32(soa, ARES_RR_SOA_REFRESH)); + values[4] = + Integer::New(env->isolate(), ares_dns_rr_get_u32(soa, ARES_RR_SOA_RETRY)); + values[5] = Integer::New(env->isolate(), + ares_dns_rr_get_u32(soa, ARES_RR_SOA_EXPIRE)); + values[6] = Integer::NewFromUnsigned( + env->isolate(), ares_dns_rr_get_u32(soa, ARES_RR_SOA_MINIMUM)); Local soa_record; if (!NewDictionaryInstance(env->context(), tmpl, values) .ToLocal(&soa_record)) { @@ -2064,25 +2195,14 @@ 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); }); - - ares_addr_port_node* cur = servers; - - for (uint32_t i = 0; cur != nullptr; ++i, cur = cur->next) { - char ip[INET6_ADDRSTRLEN]; - - const void* caddr = static_cast(&cur->addr); - int err = uv_inet_ntop(cur->family, caddr, ip, sizeof(ip)); - CHECK_EQ(err, 0); + char* csv = ares_get_servers_csv(channel->cares_channel()); + auto cleanup = OnScopeLeave([&]() { ares_free_string(csv); }); + std::vector> servers = ParseServersCsv(csv); + for (uint32_t i = 0; i < servers.size(); i++) { Local ret[] = { - OneByteString(env->isolate(), ip), - Integer::New(env->isolate(), cur->udp_port) - }; + OneByteString(env->isolate(), servers[i].first.c_str()), + Integer::New(env->isolate(), servers[i].second)}; if (server_array->Set(env->context(), i, Array::New(env->isolate(), ret, arraysize(ret)))