EV-cares

 view release on metacpan or  search on metacpan

cares.xs  view on Meta::CPAN

#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

cares.xs  view on Meta::CPAN

            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 )