Reputation: 1258
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
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:
res_init
updates it./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.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