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

        在使用 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);
}





 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值