D. J. Bernstein
Internet publication
djbdns

The dns_transmit library interface

     #include <dns.h>

     dns_transmit_start(&dt,s,flagrecursive,q,t,localip);
     dns_transmit_io(&dt,x,&deadline);
     dns_transmit_get(&dt,x,&stamp);
     dns_transmit_free(&dt);

     struct dns_transmit dt = {0};

     char s[64];
     int flagrecursive;
     char *q;
     char t[2];
     char localip[4];

     iopause_fd x[1];
     struct taia deadline;
     struct taia stamp;
The dns_transmit functions send a DNS query to some DNS servers. They save the first useful response inside dt.

The query asks for Internet records of type t for the domain name packet-encoded in q. It requests server recursion if flagrecursive is nonzero.

The IP addresses of the DNS servers are listed in s. The dns_transmit functions skip IP addresses of 0.0.0.0. The dns_transmit functions record only a pointer to the contents of s, not a copy of s, so you must leave s in place and unchanged.

The dns_transmit functions send outgoing packets from a local IP address of localip.

The dns_transmit functions act asynchronously. They are designed to be used in an iopause event loop:

     if (dns_transmit_start(&dt,s,flagrecursive,q,t,localip) == -1)
       return -1;

     for (;;) {
       int r;
       taia_now(&stamp);
       taia_uint(&deadline,120);
       taia_add(&deadline,&deadline,&stamp);
       dns_transmit_io(&dt,x,&deadline);
       iopause(x,1,&deadline,&stamp);
       r = dns_transmit_get(&dt,x,&stamp);
       if (r == -1) return -1;
       if (r == 1) break;
     }

     dosomething(dt.packet,dt.packetlen);
     dns_transmit_free(&dt);
     return 0;
dns_transmit_start begins the query; it returns 0 on success, or -1 on failure. dns_transmit_get continues the query; it returns 1 if the response has arrived, 0 if the response has not yet arrived, or -1 on failure. Here ``failure'' means a socket creation failure, a memory allocation failure, a timeout after the final query attempt, an empty list of servers (reported as EIO), a query longer than 65535 bytes (reported as EIO), a malformed response to the final query attempt (reported as EIO), or a server declaration of failure in response to the final query attempt (reported as EAGAIN).

The dns_transmit functions communicate through dt. They dynamically allocate a socket for network communication, memory for the DNS request, and memory for the DNS response; these resources are freed when you call dns_transmit_free, or when you call dns_transmit_start again with the same dt to handle another query. You must zero-initialize dt before calling dns_transmit_start the first time.

If dns_transmit_get returns 1, the DNS response is a byte string of length dt.packetlen; dt.packet points to the first byte. The IP address of the server that provided the response is stored at dt.servers + 4 * dt.curserver.

Transmission details

The dns_transmit functions send the query by UDP to the first address, wait 1 second for a response, send the query by UDP to the second address, wait 1 second for a response, etc.; then send the query by UDP to each address again, but waiting 3 seconds for each response; then again, waiting 11 seconds; then one last time, waiting 45 seconds. Recursive queries skip the 1-second step.

dns_transmit_get does not always return the first packet it sees:

  1. If the packet has the wrong ID, shows the wrong query name, shows the wrong query type, or has qdcount different from 1: dns_transmit_get discards the packet as irrelevant, and continues waiting for packets from the same server.
  2. If the packet has rcode different from 0 or 3: dns_transmit_get discards the packet, and moves immediately to the next server.
  3. If the packet has the tc bit set: dns_transmit_get tries a TCP connection to each address, waiting 10 seconds for the connection and, if a connection is established, 10 more seconds for a response.
dns_transmit_get does not listen to several servers simultaneously for responses for the same query. Each query transmission uses a new random port number and query ID.