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