Generic async DNS calls with multiple results

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

Generic async DNS calls with multiple results

Stephen R. van den Berg
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.