EV-cares
view release on metacpan or search on metacpan
#endif
/* DNS class constants */
#ifndef C_IN
#define C_IN 1
#endif
#ifndef C_CHAOS
#define C_CHAOS 3
#endif
#ifndef C_HS
#define C_HS 4
#endif
#ifndef C_ANY
#define C_ANY 255
#endif
/* helper macro for BOOT constant registration */
#define CONST_IV(stash, name) newCONSTSUB(stash, #name, newSViv(name))
/* flags that may not exist in older c-ares */
#ifndef ARES_FLAG_NO_DFLT_SVR
#define ARES_FLAG_NO_DFLT_SVR 0
#endif
#ifndef ARES_FLAG_DNS0x20
#define ARES_FLAG_DNS0x20 0
#endif
#ifndef ARES_ESERVICE
#define ARES_ESERVICE 25
#endif
#ifndef ARES_ENOSERVER
#define ARES_ENOSERVER 26
#endif
#define EV_CARES_MAGIC 0xCA7E5001
#define MAX_IO 16
#define REQUIRE_LIVE(self) \
STMT_START { \
if ((self)->magic != EV_CARES_MAGIC) croak("EV::cares: invalid object"); \
if ((self)->destroyed) croak("EV::cares: resolver is destroyed"); \
} STMT_END
#define REQUIRE_CB(cb) \
STMT_START { \
if (!(SvROK(cb) && SvTYPE(SvRV(cb)) == SVt_PVCV)) \
croak("EV::cares: callback must be a CODE reference"); \
} STMT_END
typedef struct ev_cares_s ev_cares_t;
typedef ev_cares_t *EV__cares;
typedef struct {
ev_io watcher;
ares_socket_t fd;
} ev_cares_io_t;
struct ev_cares_s {
U32 magic;
ares_channel channel;
struct ev_loop *loop;
SV *loop_sv; /* keeps a custom EV::Loop alive; NULL for default loop */
ev_timer timer;
ev_cares_io_t ios[MAX_IO];
int active_queries;
int destroyed;
int in_callback; /* prevent Safefree while callbacks run */
int free_pending; /* deferred Safefree after last callback */
int cleanup_pending; /* deferred channel teardown after last callback */
int last_timeouts; /* timeouts count from the most recent callback */
};
typedef struct {
ev_cares_t *resolver;
SV *cb;
int qtype;
int with_ttl; /* addrinfo_cb returns hashrefs when set */
int by_addr; /* host_cb returns h_name+h_aliases instead of h_addr_list */
} ev_cares_req_t;
/* forward declarations */
static void update_timer(ev_cares_t *self);
static void cleanup_now(ev_cares_t *self);
/* Bracket an ares_* call that may fire callbacks synchronously. Guards
* against the user destroying the resolver from inside such a callback: both
* the Safefree of self AND the ares_destroy of the channel are deferred until
* the outermost ares_* call has fully unwound, because the channel is still
* referenced further up the C stack until then (destroying it inline is a
* use-after-free that musl turns into a hard crash; glibc only tolerates it). */
#define ARES_CALL_BEGIN(self) (self)->in_callback++
/* Deferred teardown at the outermost unwind: cleanup_now may free self
(it consumes free_pending), so nothing may touch self after it runs. */
#define ARES_CALL_END(self) \
STMT_START { \
if (--(self)->in_callback == 0) { \
if ((self)->cleanup_pending) cleanup_now(self); \
else if ((self)->free_pending) { \
(self)->free_pending = 0; \
Safefree(self); \
} else update_timer(self); \
} else { \
update_timer(self); \
} \
} STMT_END
static void io_cb(EV_P_ ev_io *w, int revents);
static void timer_cb(EV_P_ ev_timer *w, int revents);
static void sock_state_cb(void *data, ares_socket_t fd, int readable, int writable);
static void cleanup(ev_cares_t *self);
/* qtype is set by callers that need it (only search()); Newxz zeroes it. */
static ev_cares_req_t *
new_req(pTHX_ ev_cares_t *self, SV *cb) {
ev_cares_req_t *req;
Newxz(req, 1, ev_cares_req_t);
req->resolver = self;
req->cb = SvREFCNT_inc_simple_NN(cb);
self->active_queries++;
return req;
}
/* Build a comma-separated server list. Each AV element may be either
if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVAV) {
AV *av = (AV *)SvRV(val);
if (av_len(av) < 0)
croak("EV::cares::new: 'servers' arrayref is empty");
servers = SvPV_nolen(av_to_csv(aTHX_ av));
} else {
servers = SvPV_nolen(val);
}
}
else if (strEQ(key, "rotate")) {
if (SvTRUE(val)) {
#ifdef ARES_OPT_ROTATE
optmask |= ARES_OPT_ROTATE;
#endif
}
}
#ifdef ARES_OPT_EDNSPSZ
else if (strEQ(key, "ednspsz")) {
opts.ednspsz = SvIV(val);
optmask |= ARES_OPT_EDNSPSZ;
}
#endif
#ifdef ARES_OPT_RESOLVCONF
else if (strEQ(key, "resolvconf")) {
opts.resolvconf_path = SvPV_nolen(val); /* c-ares copies internally */
optmask |= ARES_OPT_RESOLVCONF;
}
#endif
#ifdef ARES_OPT_HOSTS_FILE
else if (strEQ(key, "hosts_file")) {
opts.hosts_path = SvPV_nolen(val); /* c-ares copies internally */
optmask |= ARES_OPT_HOSTS_FILE;
}
#endif
#ifdef ARES_OPT_UDP_MAX_QUERIES
else if (strEQ(key, "udp_max_queries")) {
opts.udp_max_queries = SvIV(val);
optmask |= ARES_OPT_UDP_MAX_QUERIES;
}
#endif
#ifdef ARES_OPT_MAXTIMEOUTMS
else if (strEQ(key, "maxtimeout")) {
opts.maxtimeout = (int)(SvNV(val) * 1000);
optmask |= ARES_OPT_MAXTIMEOUTMS;
}
#endif
#ifdef ARES_OPT_QUERY_CACHE
else if (strEQ(key, "qcache")) {
opts.qcache_max_ttl = (unsigned int)SvUV(val);
optmask |= ARES_OPT_QUERY_CACHE;
}
#endif
else {
warn("EV::cares::new: unknown option '%s'", key);
}
}
Newxz(self, 1, ev_cares_t);
self->magic = EV_CARES_MAGIC;
self->loop = loop_ptr;
/* Keep the EV::Loop's blessed object alive â its DESTROY calls
ev_loop_destroy(). Holding the outer RV is not enough; inc the
underlying SV (the IV that stores the loop pointer). */
if (loop_sv) self->loop_sv = SvREFCNT_inc_simple_NN(SvRV(loop_sv));
for (i = 0; i < MAX_IO; i++)
self->ios[i].fd = ARES_SOCKET_BAD;
opts.sock_state_cb = sock_state_cb;
opts.sock_state_cb_data = self;
optmask |= ARES_OPT_SOCK_STATE_CB;
status = ares_init_options(&self->channel, &opts, optmask);
if (status != ARES_SUCCESS) {
if (self->loop_sv) SvREFCNT_dec(self->loop_sv);
Safefree(self);
croak("EV::cares::new: ares_init_options: %s", ares_strerror(status));
}
if (servers) {
status = ares_set_servers_csv(self->channel, servers);
if (status != ARES_SUCCESS) {
ares_destroy(self->channel);
if (self->loop_sv) SvREFCNT_dec(self->loop_sv);
Safefree(self);
croak("EV::cares::new: set_servers: %s", ares_strerror(status));
}
}
ev_timer_init(&self->timer, timer_cb, 0., 0.);
self->timer.data = (void *)self;
{
SV *sv = newSV(0);
sv_setref_pv(sv, class_name, (void *)self);
XPUSHs(sv_2mortal(sv));
}
}
void
resolve(self, name, cb)
EV::cares self
const char *name
SV *cb
CODE:
{
struct ares_addrinfo_hints hints;
ev_cares_req_t *req;
REQUIRE_LIVE(self);
REQUIRE_CB(cb);
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
req = new_req(aTHX_ self, cb);
ARES_CALL_BEGIN(self);
ares_getaddrinfo(self->channel, name, NULL, &hints, addrinfo_cb, req);
ARES_CALL_END(self);
}
( run in 2.633 seconds using v1.01-cache-2.11-cpan-b16cb0d3907 )