#ifndef SRC_CARES_WRAP_H_
#define SRC_CARES_WRAP_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#define CARES_STATICLIB
#include "async_wrap.h"
#include "base_object.h"
#include "env.h"
#include "memory_tracker.h"
#include "node.h"
#include "node_internals.h"
#include "permission/permission.h"
#include "util.h"
#include "ares.h"
#include "v8.h"
#include "uv.h"
#include <unordered_set>
#ifdef __POSIX__
# include <netdb.h>
#endif // __POSIX__
# include <ares_nameser.h>
namespace node {
namespace cares_wrap {
constexpr int ns_t_cname_or_a = -1;
constexpr int DNS_ESETSRVPENDING = -1000;
constexpr uint8_t DNS_ORDER_VERBATIM = 0;
constexpr uint8_t DNS_ORDER_IPV4_FIRST = 1;
constexpr uint8_t DNS_ORDER_IPV6_FIRST = 2;
class ChannelWrap;
inline void safe_free_hostent(struct hostent* host);
using HostEntPointer = DeleteFnPtr<hostent, ares_free_hostent>;
using SafeHostEntPointer = DeleteFnPtr<hostent, safe_free_hostent>;
inline const char* ToErrorCodeString(int status) {
switch (status) {
#define V(code) case ARES_##code: return #code;
V(EADDRGETNETWORKPARAMS)
V(EBADFAMILY)
V(EBADFLAGS)
V(EBADHINTS)
V(EBADNAME)
V(EBADQUERY)
V(EBADRESP)
V(EBADSTR)
V(ECANCELLED)
V(ECONNREFUSED)
V(EDESTRUCTION)
V(EFILE)
V(EFORMERR)
V(ELOADIPHLPAPI)
V(ENODATA)
V(ENOMEM)
V(ENONAME)
V(ENOTFOUND)
V(ENOTIMP)
V(ENOTINITIALIZED)
V(EOF)
V(EREFUSED)
V(ESERVFAIL)
V(ETIMEOUT)
#undef V
}
return "UNKNOWN_ARES_ERROR";
}
inline void cares_wrap_hostent_cpy(
struct hostent* dest,
const struct hostent* src) {
dest->h_addr_list = nullptr;
dest->h_addrtype = 0;
dest->h_aliases = nullptr;
dest->h_length = 0;
dest->h_name = nullptr;
/* copy `h_name` */
size_t name_size = strlen(src->h_name) + 1;
dest->h_name = node::Malloc<char>(name_size);
memcpy(dest->h_name, src->h_name, name_size);
/* copy `h_aliases` */
size_t alias_count;
for (alias_count = 0;
src->h_aliases[alias_count] != nullptr;
alias_count++) {
}
dest->h_aliases = node::Malloc<char*>(alias_count + 1);
for (size_t i = 0; i < alias_count; i++) {
const size_t cur_alias_size = strlen(src->h_aliases[i]) + 1;
dest->h_aliases[i] = node::Malloc(cur_alias_size);
memcpy(dest->h_aliases[i], src->h_aliases[i], cur_alias_size);
}
dest->h_aliases[alias_count] = nullptr;
/* copy `h_addr_list` */
size_t list_count;
for (list_count = 0;
src->h_addr_list[list_count] != nullptr;
list_count++) {
}
dest->h_addr_list = node::Malloc<char*>(list_count + 1);
for (size_t i = 0; i < list_count; i++) {
dest->h_addr_list[i] = node::Malloc(src->h_length);
memcpy(dest->h_addr_list[i], src->h_addr_list[i], src->h_length);
}
dest->h_addr_list[list_count] = nullptr;
/* work after work */
dest->h_length = src->h_length;
dest->h_addrtype = src->h_addrtype;
}
struct NodeAresTask final : public MemoryRetainer {
ChannelWrap* channel;
ares_socket_t sock;
uv_poll_t poll_watcher;
inline void MemoryInfo(MemoryTracker* trakcer) const override;
SET_MEMORY_INFO_NAME(NodeAresTask)
SET_SELF_SIZE(NodeAresTask)
struct Hash {
inline size_t operator()(NodeAresTask* a) const {
return std::hash<ares_socket_t>()(a->sock);
}
};
struct Equal {
inline bool operator()(NodeAresTask* a, NodeAresTask* b) const {
return a->sock == b->sock;
}
};
static NodeAresTask* Create(ChannelWrap* channel, ares_socket_t sock);
using List = std::unordered_set<NodeAresTask*, Hash, Equal>;
};
class ChannelWrap final : public AsyncWrap {
public:
ChannelWrap(Environment* env,
v8::Local<v8::Object> object,
int timeout,
int tries,
int max_timeout);
~ChannelWrap() override;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
void Setup();
void EnsureServers();
void StartTimer();
void CloseTimer();
void ModifyActivityQueryCount(int count);
inline uv_timer_t* timer_handle() { return timer_handle_; }
inline ares_channel cares_channel() { return channel_; }
inline void set_query_last_ok(bool ok) { query_last_ok_ = ok; }
inline void set_is_servers_default(bool is_default) {
is_servers_default_ = is_default;
}
inline int active_query_count() { return active_query_count_; }
inline NodeAresTask::List* task_list() { return &task_list_; }
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(ChannelWrap)
SET_SELF_SIZE(ChannelWrap)
static void AresTimeout(uv_timer_t* handle);
private:
uv_timer_t* timer_handle_ = nullptr;
ares_channel channel_ = nullptr;
bool query_last_ok_ = true;
bool is_servers_default_ = true;
bool library_inited_ = false;
int timeout_;
int tries_;
int max_timeout_;
int active_query_count_ = 0;
NodeAresTask::List task_list_;
};
class GetAddrInfoReqWrap final : public ReqWrap<uv_getaddrinfo_t> {
public:
GetAddrInfoReqWrap(Environment* env,
v8::Local<v8::Object> req_wrap_obj,
uint8_t order);
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(GetAddrInfoReqWrap)
SET_SELF_SIZE(GetAddrInfoReqWrap)
uint8_t order() const { return order_; }
private:
const uint8_t order_;
};
class GetNameInfoReqWrap final : public ReqWrap<uv_getnameinfo_t> {
public:
GetNameInfoReqWrap(Environment* env, v8::Local<v8::Object> req_wrap_obj);
SET_INSUFFICIENT_PERMISSION_ERROR_CALLBACK(permission::PermissionScope::kNet)
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(GetNameInfoReqWrap)
SET_SELF_SIZE(GetNameInfoReqWrap)
};
struct ResponseData final {
int status;
bool is_host;
SafeHostEntPointer host;
MallocedBuffer<unsigned char> buf;
};
template <typename Traits>
class QueryWrap final : public AsyncWrap {
public:
QueryWrap(ChannelWrap* channel, v8::Local<v8::Object> req_wrap_obj)
: AsyncWrap(channel->env(), req_wrap_obj, AsyncWrap::PROVIDER_QUERYWRAP),
channel_(channel),
trace_name_(Traits::name) {}
~QueryWrap() {
CHECK_EQ(false, persistent().IsEmpty());
// Let Callback() know that this object no longer exists.
if (callback_ptr_ != nullptr)
*callback_ptr_ = nullptr;
}
int Send(const char* name) {
return Traits::Send(this, name);
}
void AresQuery(const char* name,
ares_dns_class_t dnsclass,
ares_dns_rec_type_t type) {
permission::PermissionScope scope = permission::PermissionScope::kNet;
Environment* env_holder = env();
if (!env_holder->permission()->is_granted(env_holder, scope, name))
[[unlikely]] {
QueuePermissionModelResponseCallback(name);
return;
}
channel_->EnsureServers();
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
TRACING_CATEGORY_NODE2(dns, native), trace_name_, this,
"name", TRACE_STR_COPY(name));
ares_query_dnsrec(channel_->cares_channel(),
name,
dnsclass,
type,
Callback,
MakeCallbackPointer(),
nullptr);
}
SET_INSUFFICIENT_PERMISSION_ERROR_CALLBACK(permission::PermissionScope::kNet)
void ParseError(int status) {
CHECK_NE(status, ARES_SUCCESS);
v8::HandleScope handle_scope(env()->isolate());
v8::Context::Scope context_scope(env()->context());
const char* code = ToErrorCodeString(status);
v8::Local<v8::Value> arg = OneByteString(env()->isolate(), code);
TRACE_EVENT_NESTABLE_ASYNC_END1(
TRACING_CATEGORY_NODE2(dns, native), trace_name_, this,
"error", status);
MakeCallback(env()->oncomplete_string(), 1, &arg);
}
const BaseObjectPtr<ChannelWrap>& channel() const { return channel_; }
void AfterResponse() {
CHECK(response_data_);
int status = response_data_->status;
if (status != ARES_SUCCESS)
return ParseError(status);
if (!Traits::Parse(this, response_data_).To(&status)) {
return ParseError(ARES_ECANCELLED);
}
if (status != ARES_SUCCESS)
ParseError(status);
}
void* MakeCallbackPointer() {
CHECK_NULL(callback_ptr_);
callback_ptr_ = new QueryWrap<Traits>*(this);
return callback_ptr_;
}
static QueryWrap<Traits>* FromCallbackPointer(void* arg) {
std::unique_ptr<QueryWrap<Traits>*> wrap_ptr {
static_cast<QueryWrap<Traits>**>(arg)
};
QueryWrap<Traits>* wrap = *wrap_ptr.get();
if (wrap == nullptr) return nullptr;
wrap->callback_ptr_ = nullptr;
return wrap;
}
static void Callback(void* arg,
ares_status_t status,
size_t timeouts,
const ares_dns_record_t* dnsrec) {
QueryWrap<Traits>* wrap = FromCallbackPointer(arg);
if (wrap == nullptr) return;
unsigned char* buf_copy = nullptr;
size_t answer_len = 0;
if (status == ARES_SUCCESS) {
// No need to explicitly call ares_free_string here,
// as it is a wrapper around free, which is already
// invoked when MallocedBuffer is destructed.
ares_dns_write(dnsrec, &buf_copy, &answer_len);
}
wrap->response_data_ = std::make_unique<ResponseData>();
ResponseData* data = wrap->response_data_.get();
data->status = status;
data->is_host = false;
data->buf = MallocedBuffer<unsigned char>(buf_copy, answer_len);
wrap->QueueResponseCallback(status);
}
static void Callback(
void* arg,
int status,
int timeouts,
struct hostent* host) {
QueryWrap<Traits>* wrap = FromCallbackPointer(arg);
if (wrap == nullptr) return;
struct hostent* host_copy = nullptr;
if (status == ARES_SUCCESS) {
host_copy = node::Malloc<hostent>(1);
cares_wrap_hostent_cpy(host_copy, host);
}
wrap->response_data_ = std::make_unique<ResponseData>();
ResponseData* data = wrap->response_data_.get();
data->status = status;
data->host.reset(host_copy);
data->is_host = true;
wrap->QueueResponseCallback(status);
}
void QueuePermissionModelResponseCallback(const char* resource) {
BaseObjectPtr<QueryWrap<Traits>> strong_ref{this};
const std::string res{resource};
env()->SetImmediate([this, strong_ref, res](Environment*) {
InsufficientPermissionError(res);
// Delete once strong_ref goes out of scope.
Detach();
});
channel_->set_query_last_ok(true);
channel_->ModifyActivityQueryCount(-1);
}
void QueueResponseCallback(int status) {
BaseObjectPtr<QueryWrap<Traits>> strong_ref{this};
env()->SetImmediate([this, strong_ref](Environment*) {
AfterResponse();
// Delete once strong_ref goes out of scope.
Detach();
});
channel_->set_query_last_ok(status != ARES_ECONNREFUSED);
channel_->ModifyActivityQueryCount(-1);
}
void CallOnComplete(
v8::Local<v8::Value> answer,
v8::Local<v8::Value> extra = v8::Local<v8::Value>()) {
v8::HandleScope handle_scope(env()->isolate());
v8::Context::Scope context_scope(env()->context());
v8::Local<v8::Value> argv[] = {
v8::Integer::New(env()->isolate(), 0),
answer,
extra
};
const int argc = arraysize(argv) - extra.IsEmpty();
TRACE_EVENT_NESTABLE_ASYNC_END0(
TRACING_CATEGORY_NODE2(dns, native), trace_name_, this);
MakeCallback(env()->oncomplete_string(), argc, argv);
}
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackField("channel", channel_);
if (response_data_) {
tracker->TrackFieldWithSize("response", response_data_->buf.size);
}
}
SET_MEMORY_INFO_NAME(QueryWrap)
SET_SELF_SIZE(QueryWrap<Traits>)
private:
BaseObjectPtr<ChannelWrap> channel_;
std::unique_ptr<ResponseData> response_data_;
const char* trace_name_;
// Pointer to pointer to 'this' that can be reset from the destructor,
// in order to let Callback() know that 'this' no longer exists.
QueryWrap<Traits>** callback_ptr_ = nullptr;
};
#define QUERY_TYPES(V) \
V(Reverse, reverse, getHostByAddr) \
V(A, resolve4, queryA) \
V(Any, resolveAny, queryAny) \
V(Aaaa, resolve6, queryAaaa) \
V(Caa, resolveCaa, queryCaa) \
V(Cname, resolveCname, queryCname) \
V(Mx, resolveMx, queryMx) \
V(Naptr, resolveNaptr, queryNaptr) \
V(Ns, resolveNs, queryNs) \
V(Ptr, resolvePtr, queryPtr) \
V(Srv, resolveSrv, querySrv) \
V(Soa, resolveSoa, querySoa) \
V(Tlsa, resolveTlsa, queryTlsa) \
V(Txt, resolveTxt, queryTxt)
// All query type handlers share the same basic structure, so we can simplify
// the code a bit by using a macro to define that structure.
#define TYPE_TRAITS(Name, label) \
struct Name##Traits final { \
static constexpr const char* name = #label; \
static int Send(QueryWrap<Name##Traits>* wrap, const char* name); \
static v8::Maybe<int> Parse( \
QueryWrap<Name##Traits>* wrap, \
const std::unique_ptr<ResponseData>& response); \
}; \
using Query##Name##Wrap = QueryWrap<Name##Traits>;
#define V(NAME, LABEL, _) TYPE_TRAITS(NAME, LABEL)
QUERY_TYPES(V)
#undef V
#undef TYPE_TRAITS
} // namespace cares_wrap
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CARES_WRAP_H_