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.html
