curl-library
libcurl not refreshing DNS entries populated with CURLOPT_RESOLVE
Date: Fri, 11 Oct 2013 18:11:41 -0300
Hello,
I use the multi interface to cache the connections to some server
between requests and CURLOPT_RESOLVE to populate the DNS cache (my
scenario is a private GPRS network with no working DNS servers). But
even though I initialize the DNS entries with every easy handle
libcurl sometimes considers them expired when initiating the
connection. As a result libcurl tries to reach one of the default DNS
servers, which always results in a CURLE_COULDNT_RESOLVE_HOST in my
case.
The documentation states that CURLOPT_RESOLVE just pre-populates the
DNS cache, which implies it should follow the same rules about
expiration. But since I'm setting the option with every easy handle I
would expect the entries to be "refreshed". Moreover, there's a timing
issue: depending on how much you wait before the requests libcurl will
indeed "refresh" the entries. So, at the very least, the behaviour is
not consistent.
I attached a program demonstrating the problem (tested on
Ubuntu-64/Linux-3.8.0-31 with latest version from github:
9b33ecfd013d0713a707aec097f3fd6fe3d495a3), also available at:
https://gist.github.com/romuloceccon/6941606. It outputs the
following. Note that you can't reproduce it when omitting the call to
sleep():
debug: Added fake.host:80:137.56.161.173 to DNS cache
debug: About to connect() to fake.host port 80 (#0)
debug: Trying 137.56.161.173...
debug: Adding handle: conn: 0xb81160
debug: Adding handle: send: 0
debug: Adding handle: recv: 0
debug: Curl_addHandleToPipeline: length: 1
debug: - Conn 0 (0xb81160) send_pipe: 1, recv_pipe: 0
debug: Connected to fake.host (137.56.161.173) port 80 (#0)
debug: Server lighttpd/1.4.28 is not blacklisted
debug: Closing connection 0
HTTP transfer completed with status 0
debug: Added fake.host:80:137.56.161.173 to DNS cache
debug: getaddrinfo(3) failed for fake.host:80
debug: Couldn't resolve host 'fake.host'
debug: Closing connection 1
HTTP transfer completed with status 6
I managed to fix it by patching Curl_loadhostpairs() with code copied
from Curl_cache_addr(), but because of the timing issue I think it's
just covering the real bug somewhere else:
diff --git a/lib/hostip.c b/lib/hostip.c
index f37b492..4157a2a 100644
--- a/lib/hostip.c
+++ b/lib/hostip.c
@@ -807,8 +807,15 @@ CURLcode Curl_loadhostpairs(struct SessionHandle *data)
/* if not in the cache already, put this host in the cache */
dns = Curl_cache_addr(data, addr, hostname, port);
else
+ {
/* this is a duplicate, free it again */
Curl_freeaddrinfo(addr);
+
+ /* reset timestamp */
+ time(&dns->timestamp);
+ if(dns->timestamp == 0)
+ dns->timestamp = 1; /* zero indicates that entry isn't in hash tabl
+ }
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
/********* curl_resolve_test.c *********/
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <curl/curl.h>
size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
return size * nmemb;
}
int debug_callback(CURL *curl, curl_infotype info, char *msg, size_t
len, void *ptr)
{
if (info == CURLINFO_TEXT)
fprintf(stderr, "debug: %.*s", (int) len, msg);
return 0;
}
int do_request(CURLM *multi_handle, char const *url, char const *resolve)
{
CURL *handle;
int still_running;
int i;
int result = 0;
struct curl_slist *resolve_list = NULL;
CURLMsg *msg;
int msgs_left;
handle = curl_easy_init();
resolve_list = curl_slist_append(resolve_list, resolve);
curl_easy_setopt(handle, CURLOPT_URL, url);
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(handle, CURLOPT_RESOLVE, resolve_list);
curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, debug_callback);
curl_easy_setopt(handle, CURLOPT_VERBOSE, 1);
curl_easy_setopt(handle, CURLOPT_DNS_CACHE_TIMEOUT, 2);
curl_multi_add_handle(multi_handle, handle);
curl_multi_perform(multi_handle, &still_running);
do {
struct timeval timeout;
int rc; /* select() return code */
fd_set fdread;
fd_set fdwrite;
fd_set fdexcep;
int maxfd = -1;
long curl_timeo = -1;
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);
/* set a suitable timeout to play around with */
timeout.tv_sec = 1;
timeout.tv_usec = 0;
curl_multi_timeout(multi_handle, &curl_timeo);
if(curl_timeo >= 0) {
timeout.tv_sec = curl_timeo / 1000;
if(timeout.tv_sec > 1)
timeout.tv_sec = 1;
else
timeout.tv_usec = (curl_timeo % 1000) * 1000;
}
/* get file descriptors from the transfers */
curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
/* In a real-world program you OF COURSE check the return code of the
function calls. On success, the value of maxfd is guaranteed to be
greater or equal than -1. We call select(maxfd + 1, ...), specially in
case of (maxfd == -1), we call select(0, ...), which is basically equal
to sleep. */
rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
switch(rc) {
case -1:
/* select error */
break;
case 0: /* timeout */
default: /* action */
curl_multi_perform(multi_handle, &still_running);
break;
}
} while(still_running);
while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
if (msg->msg == CURLMSG_DONE && msg->easy_handle == handle) {
result = msg->data.result;
printf("HTTP transfer completed with status %d\n", result);
}
}
curl_multi_remove_handle(multi_handle, handle);
curl_easy_cleanup(handle);
curl_slist_free_all(resolve_list);
return result;
}
int main(void)
{
int i;
CURLM *multi;
char const *url = "http://fake.host/";
char const *resolve = "fake.host:80:137.56.161.173";
multi = curl_multi_init();
for (i = 0; i < 30; i++)
{
if (do_request(multi, url, resolve) == CURLE_COULDNT_RESOLVE_HOST)
break;
/* if you remove the following call the problem apparently goes away */
sleep(2);
}
curl_multi_cleanup(multi);
return 0;
}
/******************************************/
-- Romulo A. Ceccon ------------------------------------------------------------------- List admin: http://cool.haxx.se/list/listinfo/curl-library Etiquette: http://curl.haxx.se/mail/etiquette.htmlReceived on 2013-10-11