#include "easyxs/easyxs.h"
#include <unbound.h> /* unbound API */
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#define UNUSED(x) (void)(x)
#define DEBUG 0
#ifdef MULTIPLICITY
#define NEED_THX 1
#else
#define NEED_THX 0
#endif
#define _PERL_HAS_PL_PHASE (PERL_VERSION_GE(5, 14, 0))
#if _PERL_HAS_PL_PHASE
// TODO: Once the phase name macro is in ppport.h, use that instead.
#define _DEBUG(str, ...) if (DEBUG) fprintf(stderr, str " (phase=%s)\n", ##__VA_ARGS__, PL_phase_names[PL_phase]);
#else
#define _DEBUG(str, ...) if (DEBUG) fprintf(stderr, str " (destruct? %d)\n", ##__VA_ARGS__, PL_dirty);
#endif
#include "errcodes_define.inc"
typedef struct {
pid_t pid;
struct ub_ctx* ub_ctx;
HV* queries_hv;
unsigned refcount;
int debugfd; /* -1 means no stored debug out */
} DNS__Unbound__Context;
typedef struct {
#if NEED_THX
tTHX my_aTHX;
#endif
pid_t pid;
DNS__Unbound__Context* ctx;
int id;
SV* callback;
} dub_query_ctx_t;
// ----------------------------------------------------------------------
// A “blessed struct” is an SVPV that stores a C struct, wrapped in a
// reference SV with a bless(). This allows Perl itself to do the
// allocating and freeing of the struct, which simplfies memory management.
#define my_new_blessedstruct(type, classname) _my_new_blessedstruct_f(aTHX_ sizeof(type), classname)
#define my_get_blessedstruct_ptr(svrv) ( (void *) SvPVX( SvRV(svrv) ) )
static SV* _my_new_blessedstruct_f (pTHX_ unsigned size, const char* classname) {
SV* referent = newSV(size);
SvPOK_on(referent);
SV* reference = newRV_noinc(referent);
sv_bless(reference, gv_stashpv(classname, FALSE));
return reference;
}
// ----------------------------------------------------------------------
#define _new_dub_context_struct(ubctx) ( \
(DNS__Unbound__Context) { \
.pid = getpid(), \
.ub_ctx = ubctx, \
.queries_hv = newHV(), \
.refcount = 1, \
.debugfd = -1, \
} \
)
#define _increment_dub_ctx_refcount(ctx) STMT_START { \
ctx->refcount++; \
_DEBUG("%s: DNS__Unbound__Context %p inc refcount (now %d)", __func__, ctx, ctx->refcount); \
} STMT_END
static bool _decrement_dub_ctx_refcount (pTHX_ DNS__Unbound__Context* dub_ctx) {
if (!--dub_ctx->refcount) {
_DEBUG("Freeing DNS__Unbound__Context %p", dub_ctx);
if ((getpid() == dub_ctx->pid) && PL_dirty) {
warn("Freeing DNS::Unbound context at global destruction; memory leak likely!");
}
// We do NOT need to _close_saved_debugfd() here because
// Unbound will do that for us.
ub_ctx_delete(dub_ctx->ub_ctx);
dub_ctx->ub_ctx = NULL;
SvREFCNT_dec((SV*) dub_ctx->queries_hv);
return true;
}
_DEBUG("DNS__Unbound__Context %p dec refcount (now %d)", dub_ctx, dub_ctx->refcount);
return false;
}
// ----------------------------------------------------------------------
#define _create_query_id_str(async_id, strname) \
char strname[256]; \
snprintf(strname, sizeof(strname), "%d", async_id);
static void _store_query (pTHX_ DNS__Unbound__Context* ctx, SV* blessedstruct, int async_id, SV* callback) {
dub_query_ctx_t* query_ctx = my_get_blessedstruct_ptr(blessedstruct);
*query_ctx = (dub_query_ctx_t) {
#if NEED_THX
.my_aTHX = aTHX,
#endif
.pid = getpid(),
.ctx = ctx,
.id = async_id,
.callback = SvREFCNT_inc(callback),
};
_increment_dub_ctx_refcount(ctx);
_create_query_id_str(async_id, id_str);
hv_store(ctx->queries_hv, id_str, strlen(id_str), blessedstruct, 0);
}
static dub_query_ctx_t* _fetch_query (pTHX_ DNS__Unbound__Context* ctx, int async_id) {
_DEBUG("%s %p %d", __func__, ctx, async_id);
_create_query_id_str(async_id, id_str);
SV** entry = hv_fetch(ctx->queries_hv, id_str, strlen(id_str), 0);
// Sanity-check:
assert(entry && *entry);
_DEBUG("end %s %p %d", __func__, ctx, async_id);
return my_get_blessedstruct_ptr(*entry);
}
static void _unstore_query (pTHX_ DNS__Unbound__Context* ctx, int async_id, SV* cb_arg) {
_DEBUG("%s %p %d", __func__, ctx, async_id);
dub_query_ctx_t* query_ctx = _fetch_query(aTHX_ ctx, async_id);
SV* callback = query_ctx->callback;
_create_query_id_str(async_id, id_str);
// This will mortalize the query_ctx_svrv stored in the hash:
SV* query_ctx_svrv = hv_delete(ctx->queries_hv, id_str, strlen(id_str), 0);
PERL_UNUSED_VAR(query_ctx_svrv);
assert(query_ctx_svrv);
if (_decrement_dub_ctx_refcount(aTHX_ ctx)) {
warn("Prematurely reaped DNS::Unbound::Context?!?!?");
}
if (cb_arg) {
SV *args[] = { cb_arg, NULL };
exs_call_sv_void(callback, args);
}
SvREFCNT_dec(callback);
_DEBUG("end %s %p", __func__, ctx);
}
// ----------------------------------------------------------------------
static SV* _ub_result_to_svhv_and_free (pTHX_ struct ub_result* result) {
AV *data = newAV();
unsigned datasize = 0;
if (result->data != NULL) {
while (result->data[datasize] != NULL) {
datasize++;
}
if (datasize) {
av_extend(data, datasize - 1);
for (unsigned i=0; i<datasize; i++) {
av_store(data, i, newSVpvn(result->data[i], result->len[i]));
}
}
}
HV * rh = newHV();
hv_stores(rh, "qname", newSVpv(result->qname, 0));
hv_stores(rh, "qtype", newSViv(result->qtype));
hv_stores(rh, "qclass", newSViv(result->qclass));
hv_stores(rh, "data", newRV_noinc((SV *)data));
hv_stores(rh, "canonname", newSVpv(result->canonname, 0));
hv_stores(rh, "rcode", newSViv(result->rcode));
/* Ideally these could use boolSV(), but the efficiency gains
probably don’t justify the API change. libunbound(3) documents
these as ints, not bools, so we should preserve that. */
hv_stores(rh, "havedata", newSViv(result->havedata));
hv_stores(rh, "nxdomain", newSViv(result->nxdomain));
hv_stores(rh, "secure", newSViv(result->secure));
hv_stores(rh, "bogus", newSViv(result->bogus));
hv_stores(rh, "why_bogus",
#if HAS_WHY_BOGUS
newSVpv(result->why_bogus, 0)
#else
&PL_sv_undef
#endif
);
hv_stores(rh, "ttl",
#if HAS_TTL
newSViv(result->ttl)
#else
&PL_sv_undef
#endif
);
hv_stores(rh, "answer_packet", newSVpvn(result->answer_packet, result->answer_len));
ub_resolve_free(result);
return newRV_noinc( (SV *)rh );
}
static void _async_resolve_callback(void* mydata, int err, struct ub_result* result) {
SV* query_ctx_svrv = (SV*) mydata;
dub_query_ctx_t *query_ctx = my_get_blessedstruct_ptr(query_ctx_svrv);
#if NEED_THX
pTHX = query_ctx->my_aTHX;
#endif
_DEBUG("RESOLVE CALLBACK (ID=%d)\n", query_ctx->id);
SV* result_sv;
_DEBUG("err: %d\n", err);
if (err) {
result_sv = newSViv(err);
}
else {
result_sv = _ub_result_to_svhv_and_free(aTHX_ result);
}
_unstore_query(aTHX_ query_ctx->ctx, query_ctx->id, result_sv );
return;
}
static void _close_saved_debugfd (DNS__Unbound__Context* ctx) {
if (-1 != ctx->debugfd) close(ctx->debugfd);
}
// ----------------------------------------------------------------------
MODULE = DNS::Unbound PACKAGE = DNS::Unbound
PROTOTYPES: DISABLE
INCLUDE: errcodes_boot.inc
const char*
_get_fd_mode_for_fdopen(int fd)
CODE:
int flags = fcntl( fd, F_GETFL );
if ( flags == -1 ) {
SETERRNO( errno, 0 );
RETVAL = "";
}
else {
RETVAL = (flags & O_APPEND) ? "a" : "w";
}
OUTPUT:
RETVAL
const char *
_ub_strerror( int err )
CODE:
RETVAL = ub_strerror(err);
OUTPUT:
RETVAL
#if HAS_UB_VERSION
SV*
unbound_version(...)
CODE:
UNUSED(items);
RETVAL = newSVpv( ub_version(), 0 );
OUTPUT:
RETVAL
#endif
# ----------------------------------------------------------------------
MODULE = DNS::Unbound PACKAGE = DNS::Unbound::Context
PROTOTYPES: DISABLE
int
_ub_ctx_set_option( DNS__Unbound__Context* ctx, const char* opt, SV* val_sv)
CODE:
char *val = exs_SvPVbyte_nolen(val_sv);
RETVAL = ub_ctx_set_option(ctx->ub_ctx, opt, val);
OUTPUT:
RETVAL
void
_ub_ctx_debuglevel( DNS__Unbound__Context* ctx, int d )
CODE:
ub_ctx_debuglevel(ctx->ub_ctx, d);
void
_ub_ctx_debugout( DNS__Unbound__Context* ctx, int fd, SV *mode_sv )
CODE:
char *mode = exs_SvPVbyte_nolen(mode_sv);
FILE *fstream;
int fd_to_save = -1;
// Since libunbound does equality checks against stderr,
// let’s ensure we use that same pointer.
if (fd == fileno(stderr)) {
fstream = stderr;
}
else if (fd == fileno(stdout)) {
fstream = stdout;
}
else {
int dupfd = dup(fd);
if (-1 == dupfd) {
croak("Failed to dup(%d): %s", fd, strerror(errno));
}
// We opened it, so we need to close it:
fd_to_save = dupfd;
// Linux doesn’t care, but MacOS will segfault if you
// setvbuf() on an append stream opened on a non-append fd.
fstream = fdopen( dupfd, mode );
if (fstream == NULL) {
fprintf(stderr, "fdopen failed!!\n");
}
setvbuf(fstream, NULL, _IONBF, 0);
}
ub_ctx_debugout( ctx->ub_ctx, fstream );
_close_saved_debugfd(ctx);
// This will usually be -1:
ctx->debugfd = fd_to_save;
SV*
_ub_ctx_get_option( DNS__Unbound__Context* ctx, SV* opt)
CODE:
char *str;
char *opt_str = exs_SvPVbyte_nolen(opt);
int fate = ub_ctx_get_option(ctx->ub_ctx, opt_str, &str);
if (fate) {
// On failure, return a plain SV that gives the error.
RETVAL = newSViv(fate);
}
else {
SV *val = newSVpv(str, 0);
// On success, return a reference to an SV that gives the value.
RETVAL = newRV_noinc(val);
}
free(str);
OUTPUT:
RETVAL
int
_ub_ctx_add_ta( DNS__Unbound__Context* ctx, SV *ta )
CODE:
char *ta_str = exs_SvPVbyte_nolen(ta);
RETVAL = ub_ctx_add_ta( ctx->ub_ctx, ta_str );
OUTPUT:
RETVAL
#if HAS_UB_CTX_ADD_TA_AUTR
int
_ub_ctx_add_ta_autr( DNS__Unbound__Context* ctx, SV *fname )
CODE:
char *fname_str = exs_SvPVbyte_nolen(fname);
RETVAL = ub_ctx_add_ta_autr( ctx->ub_ctx, fname_str );
OUTPUT:
RETVAL
#endif
int
_ub_ctx_resolvconf( DNS__Unbound__Context* ctx, SV *fname_sv )
CODE:
char *fname = SvOK(fname_sv) ? exs_SvPVbyte_nolen(fname_sv) : NULL;
RETVAL = ub_ctx_resolvconf( ctx->ub_ctx, fname );
OUTPUT:
RETVAL
int
_ub_ctx_hosts( DNS__Unbound__Context* ctx, SV *fname_sv )
CODE:
char *fname = SvOK(fname_sv) ? exs_SvPVbyte_nolen(fname_sv) : NULL;
RETVAL = ub_ctx_hosts( ctx->ub_ctx, fname );
OUTPUT:
RETVAL
int
_ub_ctx_add_ta_file( DNS__Unbound__Context* ctx, SV *fname )
CODE:
char *fname_str = exs_SvPVbyte_nolen(fname);
RETVAL = ub_ctx_add_ta_file( ctx->ub_ctx, fname_str );
OUTPUT:
RETVAL
int
_ub_ctx_trustedkeys( DNS__Unbound__Context* ctx, SV *fname )
CODE:
char *fname_str = exs_SvPVbyte_nolen(fname);
RETVAL = ub_ctx_trustedkeys( ctx->ub_ctx, fname_str );
OUTPUT:
RETVAL
int
_ub_ctx_async( DNS__Unbound__Context* ctx, int dothread )
CODE:
RETVAL = ub_ctx_async( ctx->ub_ctx, dothread );
OUTPUT:
RETVAL
int
_ub_poll( DNS__Unbound__Context* ctx )
CODE:
RETVAL = ub_poll(ctx->ub_ctx);
OUTPUT:
RETVAL
int
_ub_wait( DNS__Unbound__Context* ctx )
CODE:
RETVAL = ub_wait(ctx->ub_ctx);
OUTPUT:
RETVAL
int
_ub_process( DNS__Unbound__Context* ctx )
CODE:
// Never ub_ctx_delete(ub_ctx) while using ub_ctx:
_increment_dub_ctx_refcount(ctx);
RETVAL = ub_process(ctx->ub_ctx);
_decrement_dub_ctx_refcount(aTHX_ ctx);
OUTPUT:
RETVAL
unsigned
_count_pending_queries ( DNS__Unbound__Context* ctx )
CODE:
RETVAL = hv_iterinit(ctx->queries_hv);
OUTPUT:
RETVAL
#if HAS_UB_CANCEL
int
_ub_cancel( DNS__Unbound__Context* ctx, int async_id )
CODE:
int result = ub_cancel(ctx->ub_ctx, async_id);
if (!result) {
_unstore_query(aTHX_ ctx, async_id, NULL);
}
RETVAL = result;
OUTPUT:
RETVAL
#endif
int
_ub_fd( DNS__Unbound__Context* ctx )
CODE:
RETVAL = ub_fd(ctx->ub_ctx);
OUTPUT:
RETVAL
SV*
_resolve_async( DNS__Unbound__Context* ctx, SV *name_sv, int type, int class, SV *callback )
CODE:
char *name = exs_SvPVbyte_nolen(name_sv);
int async_id = 0;
SV* query_ctx_svrv = my_new_blessedstruct(dub_query_ctx_t, "DNS::Unbound::QueryContext");
int reserr = ub_resolve_async(
ctx->ub_ctx,
name, type, class,
(void *) query_ctx_svrv, _async_resolve_callback, &async_id
);
if (reserr) {
SvREFCNT_dec(query_ctx_svrv);
}
else {
_store_query(aTHX_ ctx, query_ctx_svrv, async_id, callback);
_DEBUG("New query ID: %d", async_id);
}
AV *ret = newAV();
av_extend(ret, 1); // 2 elems - 1
av_store( ret, 0, newSViv(reserr) );
av_store( ret, 1, newSViv(async_id) );
RETVAL = newRV_noinc((SV *)ret);
OUTPUT:
RETVAL
SV*
_resolve( DNS__Unbound__Context* ctx, SV *name, int type, int class = 1 )
CODE:
struct ub_result* result;
int retval;
retval = ub_resolve(ctx->ub_ctx, exs_SvPVbyte_nolen(name), type, class, &result);
if (retval != 0) {
RETVAL = newSViv(retval);
}
else {
RETVAL = _ub_result_to_svhv_and_free(aTHX_ result);
}
OUTPUT:
RETVAL
SV*
create()
CODE:
struct ub_ctx* my_ctx = ub_ctx_create();
if (!my_ctx) {
croak("Failed to create Unbound context!");
}
SV* dub_ctx_sv = my_new_blessedstruct(DNS__Unbound__Context, "DNS::Unbound::Context");
DNS__Unbound__Context* dub_ctx = my_get_blessedstruct_ptr(dub_ctx_sv);
*dub_ctx = _new_dub_context_struct(my_ctx);
RETVAL = dub_ctx_sv;
OUTPUT:
RETVAL
void
DESTROY (DNS__Unbound__Context* dub_ctx)
CODE:
_DEBUG("%s", __func__);
_decrement_dub_ctx_refcount(aTHX_ dub_ctx);
# ----------------------------------------------------------------------
MODULE = DNS::Unbound PACKAGE = DNS::Unbound::QueryContext
void
DESTROY (SV* self_sv)
CODE:
_DEBUG("%s", __func__);
dub_query_ctx_t* query_ctx = my_get_blessedstruct_ptr(self_sv);
if ((getpid() == query_ctx->pid) && PL_dirty) {
warn("Freeing %" SVf " at global destruction; memory leak likely!", self_sv);
}