您的位置:首页 > 其它

libcurl 一旦出现连接失败后,后续请求该域名,DNS则不会重新解析该域名(libcurl-7.35.0)

2016-02-19 08:32 1201 查看
        在使用 multi interface过程中,出现了一个问题。/etc/hosts中某域名的 ip下架了,然后修改为最新的 ip, 但是程序请求该域名解析出来的IP依旧是之前已经下架了得
ip地址。在不重启程序的情况下,这显然是错误的。

        查阅资料得知,libcurl 库中存在dns cache, 默认的DNS_CACHE_TIMEOUT 为60秒,只有在DNS_CACHE_TIMEOUT与使用缓存引用计数为零同时满足的情况下,才会重新解析域名。

        接着,测试将原来存在的ip 改为另一个存在的ip,发现60s之后是正常重新解析请求的, 改为一个不存在的ip,之后也是正常的。接着从不存在的改为存在的ip,就出现以上的问题,不会再解析。

        所以简单得出测试结论,一旦socket连接失败,后续怎么改ip ,libcurl都不会去重新解析域名。
        写了一个 easy interface demo, 发现在DNS_CACHE_TIMEOUT之后,解析是正常的,不存在该问题。
        查看当前libcurl版本为7.35.0,下载最新的7.47.1版本,经过测试,发现一切都正常。

        调试libcurl流程,添加相关日志,然后开启libcurl库的DEBUG日志 。对比两个版本,发现新版本中连接失败的情况下,会对引用计数减减,而当前版本则没有该操作。
       
        libcurl-7.35.0 中 dns 重新解析的条件是 DNS_CACHE_TIMEOUT
并且引用计数为零。
        当连接失败后,则不会对引用计数递减. 即接下来引用计数都不会为零,则不会重新解析域名。
        假若更改系统配置域名对应的IP, 不重启程序,后续则会一直使用缓存中错的IP.

        libcurl-7.47.1(当前最新) 中 dns 重新解析的条件是 DNS_CACHE_TIMEOUT(机制改变了,对比libcurl7.35.0版本引用计数此时在这不做判断)
        当连接失败后,依旧会对引用计数递减。一旦 DNS_CACHE_TIMEOUT,则会立马重新解析。

        所以该问题解决办法: 升级版本即可

相关源码分析如下:

libcurl-7.47.0:

域名解析的流程:
lib/hostip.c

int Curl_resolv(struct connectdata *conn, const char *hostname, int port, struct Curl_dns_entry **entry)
{

...
    dns = fetch_addr(conn, hostname, port); //取DNS缓存
...
}

/* lookup address, returns entry if found and not stale */
static struct Curl_dns_entry * fetch_addr(struct connectdata *conn, const char *hostname, int port)
{

...
     if(hostcache_timestamp_remove(&user, dns)) {
      infof(data, "Hostname in DNS cache was stale, zapped\n");
      dns = NULL; /* the memory deallocation is being handled by the hash */
      Curl_hash_delete(data->dns.hostcache, entry_id, entry_len+1);
    }
...
}

/*
 * This function is set as a callback to be called for every entry in the DNS
 * cache when we want to prune old unused entries.
 *
 * Returning non-zero means remove the entry, return 0 to keep it in the
 * cache.
 */
static int hostcache_timestamp_remove(void *datap, void *hc)
{
  struct hostcache_prune_data *data =
    (struct hostcache_prune_data *) datap;
  struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc;

  return (0 != c->timestamp)  && (data->now - c->timestamp >= data->cache_timeout);
}

连接时的流程:
lib/multi.c

static CURLMcode multi_runsingle(struct Curl_multi *multi, struct timeval now, struct SessionHandle *data)

{
...
      if(timeout_ms < 0) {
        /* Handle timed out */
        if(data->mstate == CURLM_STATE_WAITRESOLVE)
          failf(data, "Resolving timed out after %ld milliseconds",
                Curl_tvdiff(now, data->progress.t_startsingle));
        else if(data->mstate == CURLM_STATE_WAITCONNECT)
          failf(data, "Connection timed out after %ld milliseconds",
                Curl_tvdiff(now, data->progress.t_startsingle));
        else {
          k = &data->req;
          if(k->size != -1) {
            failf(data, "Operation timed out after %ld milliseconds with %"
                  CURL_FORMAT_CURL_OFF_T " out of %"
                  CURL_FORMAT_CURL_OFF_T " bytes received",
                  Curl_tvdiff(k->now, data->progress.t_startsingle),
                  k->bytecount, k->size);
          }
          else {
            failf(data, "Operation timed out after %ld milliseconds with %"
                  CURL_FORMAT_CURL_OFF_T " bytes received",
                  Curl_tvdiff(now, data->progress.t_startsingle),
                  k->bytecount);
          }
        }

        /* Force connection closed if the connection has indeed been used */
        if(data->mstate > CURLM_STATE_DO) {
          connclose(data->easy_conn, "Disconnected with pending data");
          disconnect_conn = TRUE;
        }
        result = CURLE_OPERATION_TIMEDOUT;
        (void)Curl_done(&data->easy_conn, result, TRUE);
        /* Skip the statemachine and go directly to error handling section. */
        goto statemachine_end;
      }

...   

}

lib/url.c

CURLcode Curl_done(struct connectdata **connp,  CURLcode status,  /* an error if this is called after an 
                                   error was detected */
                   bool premature)
{

...
     if(conn->dns_entry) {
    Curl_resolv_unlock(data, conn->dns_entry); /* done with this */
    conn->dns_entry = NULL;
  }
...
}

lib/hostip.c

void Curl_resolv_unlock(struct SessionHandle *data, struct Curl_dns_entry *dns)
{
  if(data && data->share)
    Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);

  freednsentry(dns);

  if(data && data->share)
    Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
}

/*
 * File-internal: release cache dns entry reference, free if inuse drops to 0
 */
static void freednsentry(void *freethis)
{
  struct Curl_dns_entry *dns = (struct Curl_dns_entry *) freethis;
  DEBUGASSERT(dns && (dns->inuse>0));

  dns->inuse--;
  if(dns->inuse == 0) {
    Curl_freeaddrinfo(dns->addr);
    free(dns);
  }
}

libcurl-7.35.0:

域名解析的流程:
lib/hostip.c

int Curl_resolv(struct connectdata *conn, const char *hostname, int port, struct Curl_dns_entry **entry)
{
...
     /* See whether the returned entry is stale. Done before we release lock */
  if(remove_entry_if_stale(data, dns)) {
    infof(data, "Hostname in DNS cache was stale, zapped\n");
    dns = NULL; /* the memory deallocation is being handled by the hash */
  }
...
}

/*
 * Check if the entry should be pruned. Assumes a locked cache.
 */
static int
remove_entry_if_stale(struct SessionHandle *data, struct Curl_dns_entry *dns)
{
...
      if(!hostcache_timestamp_remove(&user,dns) )
    return 0;
....
}

/*
 * This function is set as a callback to be called for every entry in the DNS
 * cache when we want to prune old unused entries.
 *
 * Returning non-zero means remove the entry, return 0 to keep it in the
 * cache.
 */
static int hostcache_timestamp_remove(void *datap, void *hc)
{
  struct hostcache_prune_data *data =
    (struct hostcache_prune_data *) datap;
  struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc;

  return !c->inuse && (data->now - c->timestamp >= data->cache_timeout);
}

连接时的流程:

lib/multi.c

static CURLMcode multi_runsingle(struct Curl_multi *multi, struct timeval now, struct SessionHandle *data)

{
...

      if(timeout_ms < 0) {
        /* Handle timed out */
        if(data->mstate == CURLM_STATE_WAITRESOLVE)
          failf(data, "Resolving timed out after %ld milliseconds",
                Curl_tvdiff(now, data->progress.t_startsingle));
        else if(data->mstate == CURLM_STATE_WAITCONNECT)
          failf(data, "Connection timed out after %ld milliseconds",
                Curl_tvdiff(now, data->progress.t_startsingle));
        else {
          k = &data->req;
          if(k->size != -1) {
            failf(data, "Operation timed out after %ld milliseconds with %"
                  CURL_FORMAT_CURL_OFF_T " out of %"
                  CURL_FORMAT_CURL_OFF_T " bytes received",
                  Curl_tvdiff(k->now, data->progress.t_startsingle),
                  k->bytecount, k->size);
          }
          else {
            failf(data, "Operation timed out after %ld milliseconds with %"
                  CURL_FORMAT_CURL_OFF_T " bytes received",
                  Curl_tvdiff(now, data->progress.t_startsingle),                  k->bytecount);
          }
        }

        /* Force the connection closed because the server could continue to
           send us stuff at any time. (The disconnect_conn logic used below
           doesn't work at this point). */
        data->easy_conn->bits.close = TRUE;
        data->result = CURLE_OPERATION_TIMEDOUT;
        multistate(data, CURLM_STATE_COMPLETED);
        break;
      }

...
}

不会调用 Curl_done(), 则会导致 inuse 不为0

lib/url.c

CURLcode Curl_done(struct connectdata **connp,
                   CURLcode status,  /* an error if this is called after an
                                        error was detected */
                   bool premature)
{

...
      if(conn->dns_entry) {
    Curl_resolv_unlock(data, conn->dns_entry); /* done with this */
    conn->dns_entry = NULL;
  }
...
}

lib/hostip.c

void Curl_resolv_unlock(struct SessionHandle *data, struct Curl_dns_entry *dns)
{
  DEBUGASSERT(dns && (dns->inuse>0));

  if(data && data->share)
    Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);

  dns->inuse--;
  /* only free if nobody is using AND it is not in hostcache (timestamp ==
     0) */
  if(dns->inuse == 0 && dns->timestamp == 0) {
    Curl_freeaddrinfo(dns->addr);
    free(dns);
  }

  if(data && data->share)
    Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
}

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息