cURL / Mailing Lists / curl-library / Single Mail

curl-library

libcurl not refreshing DNS entries populated with CURLOPT_RESOLVE

From: Romulo Ceccon <romuloceccon_at_gmail.com>
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.html
Received on 2013-10-11