C/C++ Cross-Reference Tool

DNS Records Query with res_query

We all know the most usual DNS queries, i.e. mapping a name to an IP address. This is performed through the getaddrinfo() call (replacement of gethostbyname). But the DNS is much more than this mapping! It holds among other records the mail servers address for a given name, etc.

In this short article we write a small program that queries the DNS for the mail server associated to a domain name. It can be easily adapted to query any other DNS record.

Apart from the name/address mapping there is no general library call that given a name and a type (that's the name for a specific DNS record) would return the associated address. That's why we are relying here on the res_query call part of the resolv library.

As this call will only build the request, send it to the DNS and then return the buffer containing the reply, we need to dig a little bit in the DNS reply format. It is described in the RFC 1035.

We will just remind here what is needed for our small program. Please refer to the RFC for the full description.

A reply consists of a header, followed by possibly four blocks:

  • question
  • answers
  • name servers
  • address records

Depending on the header contents, these fields are present or not. The header contains the number of each of these records. In the resolv library, it is represented by the HEADER structure.

The question record consists of a domain name, followed by the query type and the query class.

The three remaining records have a generic format:

  • domain name
  • type (2 bytes)
  • class (2 bytes)
  • TTL (4 bytes)
  • length of data (2 bytes)
  • data

Data are interpreted depending on type and class. For example, for an MX record, data consist of two fields: the preference and the mail server name. The RFC contains the full list of records and their interpretation.

As the name in the record may be compressed, we use the library functions to read and print it.

The program is actually quite simple. First, we make the DNS query using res_query, the generic method that allows us to supply the type of record we are interested in:

    const size_t size = 1024;
    unsigned char buffer[size];

    const char *host = argv[1];

    int r = res_query (host, C_IN, T_MX, buffer, size);
    if (r == -1) {
        std::cerr << h_errno << " " << hstrerror (h_errno) << "\n";
        return 1;
    else {
        if (r == static_cast<int> (size)) {
            std::cerr << "Buffer too small reply truncated\n";
            return 1;

Then we check the reply for errors sent by the server:

    HEADER *hdr = reinterpret_cast<HEADER*> (buffer);

    if (hdr->rcode != NOERROR) {

        std::cerr << "Error: ";
        switch (hdr->rcode) {
        case FORMERR:
            std::cerr << "Format error";
        case SERVFAIL:
            std::cerr << "Server failure";
        case NXDOMAIN:
            std::cerr << "Name error";
        case NOTIMP:
            std::cerr << "Not implemented";
        case REFUSED:
            std::cerr << "Refused";
            std::cerr << "Unknown error";
        return 1;

And we handle now the reply. First we read the header to get the number of each of records:

    int question = ntohs (hdr->qdcount);
    int answers = ntohs (hdr->ancount);
    int nameservers = ntohs (hdr->nscount);
    int addrrecords = ntohs (hdr->arcount);

    std::cout << "Reply: question: " << question << ", answers: " << answers
              << ", nameservers: " << nameservers
              << ", address records: " << addrrecords << "\n";

Then we go through each record in turn: first the question specifically, then the last three using the same parse function. The resolv library supplies several helpers to read the received buffer:

    ns_msg m;
    int k = ns_initparse (buffer, r, &m);
    if (k == -1) {
        std::cerr << errno << " " << strerror (errno) << "\n";
        return 1;

ns_initparse reads the buffer and fills the ns_msg structure with its contents.

We are now ready to read the reply: first the question record using macros to access the fields of the ns_rr data structure and then the remaining records.

    for (int i = 0; i < question; ++i) {
        ns_rr rr;
        int k = ns_parserr (&m, ns_s_qd, i, &rr);
        if (k == -1) {
            std::cerr << errno << " " << strerror (errno) << "\n";
            return 1;
        std::cout << "question " << ns_rr_name (rr) << " "
                  << ns_rr_type (rr) << " " << ns_rr_class (rr) << "\n";
    for (int i = 0; i < answers; ++i) {
        parse_record (buffer, r, "answers", ns_s_an, i, &m);

    for (int i = 0; i < nameservers; ++i) {
        parse_record (buffer, r, "nameservers", ns_s_ns, i, &m);

    for (int i = 0; i < addrrecords; ++i) {
        parse_record (buffer, r, "addrrecords", ns_s_ar, i, &m);

And finally the generic parse record function. It only parses a small subset of the available records (A, NS, and MX). It unpacks the domain name as they may be compressed in the record:

void parse_record (unsigned char *buffer, size_t r,
                   const char *section, ns_sect s,
                   int idx, ns_msg *m) {

    ns_rr rr;
    int k = ns_parserr (m, s, idx, &rr);
    if (k == -1) {
        std::cerr << errno << " " << strerror (errno) << "\n";

    std::cout << section << " " << ns_rr_name (rr) << " "
              << ns_rr_type (rr) << " " << ns_rr_class (rr)
              << ns_rr_ttl (rr) << " ";

    const size_t size = NS_MAXDNAME;
    unsigned char name[size];
    int t = ns_rr_type (rr);

    const u_char *data = ns_rr_rdata (rr);
    if (t == T_MX) {
        int pref = ns_get16 (data);
        ns_name_unpack (buffer, buffer + r, data + sizeof (u_int16_t),
                        name, size);
        char name2[size];
        ns_name_ntop (name, name2, size);
        std::cout << pref << " " << name2;
    else if (t == T_A) {
        unsigned int addr = ns_get32 (data);
        struct in_addr in;
        in.s_addr = ntohl (addr);
        char *a = inet_ntoa (in);
        std::cout << a;
    else if (t == T_NS) {
        ns_name_unpack (buffer, buffer + r, data, name, size);
        char name2[size];
        ns_name_ntop (name, name2, size);
        std::cout << name2;
    else {
        std::cout << "unhandled record";

    std::cout << "\n";

To compile this program, you have to link against the resolv library (-lresolv on your compiler command line).

When running it, you should get the following output:

$ ./a.out
Reply: question: 1, answers: 5, nameservers: 0, address records: 5
question 15 1
answers 15 11660 20
answers 15 11660 40
answers 15 11660 10
answers 15 11660 50
answers 15 11660 30
addrrecords 1 1250
addrrecords 1 11660
addrrecords 1 11335
addrrecords 1 11393
addrrecords 1 11225

Within the resolv library, the res_mkquery() call builds a query but does not make the DNS request. It can be used for example to perform DNS queries in non-blocking mode.

Source Code

The source file of the implementation can be found here.

Comments !