struggling_learner
struggling_learner

Reputation: 1258

res_query not following res_init, unsets what was set

Please consider this code, where I am calling res_init(), and setting 192.168.1.77 as the sole nameserver. However when res_query runs, apparantly it is re-doing the res_init, and going back to the first 3 nameservers from resolv.conf.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <resolv.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>


int main (int argc, char *argv[])
{
    u_char nsbuf[4096];
    char dispbuf[4096];
    ns_msg msg;
    ns_rr rr;
    int i, j, l;

    if (argc < 2) {
        printf ("Usage: %s <domain>[...]\n", argv[0]);
        exit (1);
    }
    res_init();
    char str[INET_ADDRSTRLEN];

    for (int i = 0 ; i < _res.nscount; i++){
        inet_ntop(AF_INET,&(_res.nsaddr_list[i].sin_addr.s_addr) , str, INET_ADDRSTRLEN);
        printf("Before:  nameserver %d : %s\n", i,str);
    }

    // set to use 192.168.1.77 as nameserver
     _res.nscount = 1;
     _res.nsaddr_list[0].sin_family = AF_INET;
     _res.nsaddr_list[0].sin_addr.s_addr = inet_addr("192.168.1.77");
     _res.nsaddr_list[0].sin_port = htons(53);

    for (int i = 0 ; i < _res.nscount; i++){
        inet_ntop(AF_INET,&(_res.nsaddr_list[i].sin_addr.s_addr) , str, INET_ADDRSTRLEN);
        printf("After:  nameserver %d : %s\n", i,str);
    }

    for (i = 1; i < argc; i++) {
    printf("ns count before res_query %d\n", _res.nscount);
       l = res_query (argv[i], ns_c_any, ns_t_a, nsbuf, sizeof (nsbuf));
    printf("ns count after res_query %d\n", _res.nscount);

    for (int i = 0 ; i < _res.nscount; i++){
        inet_ntop(AF_INET,&(_res.nsaddr_list[i].sin_addr.s_addr) , str, INET_ADDRSTRLEN);
        printf("After res_query:  nameserver %d : %s\n", i,str);
    }

        if (l < 0) {
            perror (argv[i]);
        } else {
            ns_initparse (nsbuf, l, &msg);
            printf ("---------------------\n%s :\n", argv[i]);
            l = ns_msg_count (msg, ns_s_an);
            for (j = 0; j < l; j++) {
                ns_parserr (&msg, ns_s_an, j, &rr);
                ns_sprintrr (&msg, &rr, NULL, NULL, dispbuf, sizeof (dispbuf));
                printf ("%s\n", dispbuf);
            }
        }
    }

    exit (0);
}

Below is the output where we can see this happening:

$ ./a.out cnn.com
Before:  nameserver 0 : 127.0.0.1
Before:  nameserver 1 : 192.168.1.1
Before:  nameserver 2 : 1.1.1.1
After:  nameserver 0 : 192.168.1.77
ns count before res_query 1
ns count after res_query 3
After res_query:  nameserver 0 : 127.0.0.1     /// <----- these... 
After res_query:  nameserver 1 : 192.168.1.1   /// <----- these... 
After res_query:  nameserver 2 : 1.1.1.1       /// <----- these... 
---------------------
cnn.com :
cnn.com.        58S IN A    151.101.129.67
cnn.com.        58S IN A    151.101.1.67
cnn.com.        58S IN A    151.101.65.67
cnn.com.        58S IN A    151.101.193.67

What am I doing wrong? How can I do res_query without it doing another res_init() behind my back?

Update 1

Thank you for the detailed and thorough reply. Below, I am able to retain the nameservers I add, after I've modified to call res_init, and then res_mkquery().

However, it still more of a miss:

 $ time ./warm cnn.com 8.8.8.8
PACKETSZ: 512
Before:  nameserver 0 : 127.0.0.1
Before:  nameserver 1 : 192.168.1.1
Before:  nameserver 2 : 1.1.1.1
queryLen is 25
setting 8.8.8.8 as nameserver
After:  nameserver 0 : 8.8.8.8
ns count before res_query 1
res_query failed!
ns count after res_query 1
After res_query:  nameserver 0 : 8.8.8.8
cnn.com: Connection timed out
ns count before res_query 1
res_query failed!
ns count after res_query 1
After res_query:  nameserver 0 : 8.8.8.8
8.8.8.8: Connection timed out

real    0m0.131s
user    0m0.008s
sys     0m0.000s

Although I see traffic going to 8.8.8.8.

warm.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <resolv.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>


int main (int argc, char *argv[])
{
    u_char nsbuf[4096];
    u_char warm_buf[4096];
    char dispbuf[4096];

    //char nameserver[] = "127.0.0.1";
    //char nameserver[] = "192.168.1.166";
    char *nameserver = argv[2];
    ns_msg msg;
    ns_rr rr;
    int i, j, l;

    if (argc < 2) {
        printf ("Usage: %s <domain>[...]\n", argv[0]);
        exit (1);
    }


    char str[INET_ADDRSTRLEN];

     //res_init();

    printf("PACKETSZ: %d\n",PACKETSZ);
    for (int i = 0 ; i < _res.nscount; i++){
        inet_ntop(AF_INET,&(_res.nsaddr_list[i].sin_addr.s_addr) , str, INET_ADDRSTRLEN);
        printf("Before:  nameserver %d : %s\n", i,str);
    }
     int queryLen = res_mkquery(
                     ns_o_query,      /* regular query         */
                     argv[1],          /* the domain to look up */
                     ns_c_any,         /* Internet type         */
                     ns_t_a,        /* Look up an A record */
                     (u_char *)NULL,  /* always NULL       */
                     0,               /* length of NULL        */
                     (u_char *)NULL,  /* always NULL       */
                     warm_buf,/* buffer for the query  */
                     sizeof(warm_buf));  /* size of the buffer    */


    printf("queryLen is %d\n" , queryLen);
    //printf("queryLen is %d\n" , strlen(warm_buf) );

    // set to use 192.168.1.77 as nameserver
    printf("setting %s as nameserver\n", nameserver);
     _res.nscount = 1;
     _res.nsaddr_list[0].sin_family = AF_INET;
     _res.nsaddr_list[0].sin_addr.s_addr = inet_addr(nameserver);
     _res.nsaddr_list[0].sin_port = htons(53);

    for (int i = 0 ; i < _res.nscount; i++){
        inet_ntop(AF_INET,&(_res.nsaddr_list[i].sin_addr.s_addr) , str, INET_ADDRSTRLEN);
        printf("After:  nameserver %d : %s\n", i,str);
    }

    for (i = 1; i < argc; i++) {
    printf("ns count before res_query %d\n", _res.nscount);
       l = res_query (argv[i], ns_c_any, ns_t_a, nsbuf, sizeof (nsbuf));
    printf(" -------------------------- >>> l is %d\n", l);
    printf("ns count after res_query %d\n", _res.nscount);

    for (int i = 0 ; i < _res.nscount; i++){
        inet_ntop(AF_INET,&(_res.nsaddr_list[i].sin_addr.s_addr) , str, INET_ADDRSTRLEN);
        printf("After res_query:  nameserver %d : %s\n", i,str);
    }

        if (l < 0) {
            perror (argv[i]);
        } else {
            ns_initparse (nsbuf, l, &msg);
            printf ("---------------------\n%s :\n", argv[i]);
            l = ns_msg_count (msg, ns_s_an);
            for (j = 0; j < l; j++) {
                ns_parserr (&msg, ns_s_an, j, &rr);
                ns_sprintrr (&msg, &rr, NULL, NULL, dispbuf, sizeof (dispbuf));
                printf ("%s\n", dispbuf);
            }
        }
    }

    exit (0);
}

Upvotes: 1

Views: 774

Answers (1)

Florian Weimer
Florian Weimer

Reputation: 33719

Debian 9 (stretch) and earlier versions use a custom, downstream-only patch to automatically reload /etc/resolv.conf if it has changed. The way this patch is written, the cache is only updated when the internal __res_maybe_init function is called, which res_init does not do. This means that a call to the actual resolver functions (such as res_query) results in a cold cache in __res_maybe_init, a reload occurs, and your changes to _res are thrown away.

Upstream implemented automatic /etc/resolv.conf reloading glibc 2.26. The upstream approach is very different and attempts to handle _res patching by applications:

  • There is a unified cache, and calling res_init updates it.
  • If a reload of /etc/resolv.conf is necessary, glibc checks if the _res values still reflect what was previously loaded from /etc/resolv.conf, and if the values a different, the new /etc/resolv.conf contents is not applied and the current _res settings are not overwritten.
  • There is a new RES_NORELOAD/noreload resolver option which disables automated reloading completely.

These changes will be part of the upcoming Debian version 10 (buster).

If you do not want to upgrade glibc, you can make it much less likely that the Debian-specific reloading code overrides your changes if you trigger a call to __res_maybe_init before patching _res, instead of calling res_init. One way to do this without sending a query is to call res_mkquery with some dummy arguments. This will pre-warm the cache, and a reload will only occur if the /etc/resolv.conf file is changed on disk (at which point your changes will still be overwritten—I do not think there is a way to prevent this with the old Debian versions).

Upvotes: 1

Related Questions