I'm writing a generic HTTP server and will probably be writing
a full-fledged SMTP server as well (queueing messages in a database,
automatic DKIM signage, full MX-DNS support with retries and
automatic fallback/backoff for recipient servers that don't allow
many recipients or many connections or many messages per second).
For the HTTP server I need multiple IP addresses per domainquery
(I need to bind to all interfaces), and for the SMTP server
I need to query multiple TXT records (SPF validation) and
need to get back preference-sorted MX records, of which I then
need multiple IP addresses again to try and connect to
all IP addresses. For the both servers I need to know the
difference between a (possibly temporary) failure of the query
and a definitive result that says the record does not exist.
For this purpose I created a small generic dnsquery() function
(see below). Is it something worth including in the
Protocols.DNS module, or is it better left outside?
The problem was/is, none of the existing convenience
functions seems to allow me to do this.
Protocols.DNS.async_client dnsclient = Protocols.DNS.async_client();
private void dnscb(string domain, mapping res,
function(array, array:void) cb, array restargs) {
array an = res->an;
switch (res->rcode) {
case Protocols.DNS.SERVFAIL:
case Protocols.DNS.NOTIMP:
case Protocols.DNS.REFUSED:
case Protocols.DNS.YXDOMAIN:
case Protocols.DNS.YXRRSET:
case Protocols.DNS.NXRRSET:
case Protocols.DNS.NOTZONE:
if (!sizeof(an)) {
cb(0, restargs);
return;
}
}
sort(an->preference, an);
cb(an->aaaa + an->a + an->mx + an->txt - ({ 0 }), restargs);
}
private mapping dnstypetonum = ([
"A": Protocols.DNS.T_A,
"MX": Protocols.DNS.T_MX,
"TXT": Protocols.DNS.T_TXT,
"AAAA": Protocols.DNS.T_AAAA,
]);
private void collect2aaaa(array|zero results, array restargs) {
array|zero prevresults = restargs[1];
if (results) {
if (prevresults)
results -= prevresults;
else
prevresults = ({});
prevresults += results;
}
if (--restargs[0])
restargs[1] = prevresults;
else
restargs[2](prevresults, restargs[3..]);
}
//! Asynchronous DNS query with multiple results and a distinction
//! between failure and empty results.
//!
//! @param type
//! DNS query type (case sensitive). Currently supported: A, MX, TXT, AAAA.
//! Querying for AAAA gives both IPv4 and IPv6 results.
//!
//! @param domain
//! The domain name we are querying.
//!
//! @param result_cb
//! The callback function that receives the result of the DNS query.
//! It is called as follows:
//! @expr{void result_cb(array(string)|zero results,array restargs);@}
//! If the request fails it will return @expr{zero@} for @expr{results@}.
//! @expr{restargs@} is passed unaltered to the callback function.
//!
//! @note
//! There is a notable difference between @expr{results@} equal
//! to @expr{zero@} (= request failed and can be retried) and
//! @expr{({})@} (= request definitively answered the record
//! does not exist; retries are pointless).
void dnsquery(string type, string domain,
function(array(string)|zero, array:void) result_cb, array restargs) {
int itype = dnstypetonum[type];
if (!itype) {
result_cb(0, restargs);
return;
}
if (type == "AAAA") {
restargs = ({ 2, 0, result_cb }) + restargs;
dnsclient->do_query(domain, Protocols.DNS.C_IN, dnstypetonum["A"],
dnscb, collect2aaaa, restargs);
result_cb = collect2aaaa;
}
dnsclient->do_query(domain, Protocols.DNS.C_IN, itype,
dnscb, result_cb, restargs);
}
--
Stephen.