最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

CURL偶发超时时间不受限制?

XAMPP相关 中文小张 1457浏览 0评论

一、起因
8月20日接到一个任务,通过抓取接口的响应日志,分析资讯抓取时间分布。拉取日志,发现一些异常情况:

CURL抓取超时时间设置2s,链接超时时间设置为1s,但日志中偶发一些时间超过3s的请求,如下:

CURLOPT_CONNECTTIMEOUT => 1,
CURLOPT_TIMEOUT => 2,
function设置的超时时间
2019-08-21 05:15:17  error  50.0426
{
“url”:””,
“content_type”:null,
“http_code”:0,
“header_size”:0,
“request_size”:0,
“filetime”:-1,
“ssl_verify_result”:0,
“redirect_count”:0,
“total_time”:50.042181,
“namelookup_time”:0,
“error_info”:”Resolving timed out after 1510 milliseconds”
}

为了减少长度,删除了部分无用参数

请求总时间 50.0426s ,error_info提示解析超时,明明设置了超时时间,为什么依然会有50s?

二、分析

原因查找:
查看libcurl文档,发现DNS解析超时,原因一般情况下有两种:

设置CURLOPT_NOSIGNAL值为1,会导致解析DNS解析时间不受超时时间控制。

#ifdef USE_ALARM_TIMEOUT
if(data->set.no_signal)
/* Ignore the timeout when signals are disabled */
timeout = 0;
else
timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms;
if(!timeout)
/* USE_ALARM_TIMEOUT defined, but no timeout actually requested */
return Curl_resolv(conn, hostname, port, TRUE, entry);
if(timeout < 1000) {
/* The alarm() function only provides integer second resolution, so if
we want to wait less than one second we must bail out already now. */
failf(data,
“remaining timeout of %ld too small to resolve via SIGALRM method”,
timeout);
return CURLRESOLV_TIMEDOUT;
}

系统启用了IPv6

When c-ares isn’t enabled, libcurl by default calls getaddrinfo with
family set to PF_UNSPEC which causes getaddrinfo to return all
available addresses, both IPv4 and IPv6. Libcurl then tries each one
until it can connect. If the net connection doesn’t support IPv6,
libcurl can still fall back to IPv4.

分析排查:
首先排除第一种情况,CURL请求function没有设置CURLOPT_NOSIGNAL(默认值0)

CURL库没有开启c-ares。
那么接下来是找运维,排查编译PHP是否禁用IPv6以及系统是否禁用IPv6

1、从PHP编译参数看,已经禁用了IPv6

./configure \
–prefix=${INSTALL_DIR} \
–with-config-file-path=${INSTALL_DIR}/etc \
–disable-ipv6 \

2、centos也没有IPv6地址存在

$ lsmod | grep ipv6

此时有点疑惑了,确实没有启用IPv6,为什么依然会超时?

猜测:是不是需要明确指定IPv4?至于为什么,还不清楚。先干起来试试吧!

修改CURL请求function

curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);

提交代码,等待新的日志,再继续分析….

在此等待过程中,假设猜测正确,为什么确实没有开启IPv6,但结果并非如此呢。再次打开官方文档、下载libcurl库源码寻找可能原因。

第一步:先看看libcurl对于CURLOPT_IPRESOLVE的定义

#endif
/* Below here follows defines for the CURLOPT_IPRESOLVE option. If a host
name resolves addresses using more than one IP protocol version, this
option might be handy to force libcurl to use a specific IP version. */
#define CURL_IPRESOLVE_WHATEVER 0 /* default, resolves addresses to all IP versions that your system allows */
#define CURL_IPRESOLVE_V4       1 /* resolve to IPv4 addresses */
#define CURL_IPRESOLVE_V6       2 /* resolve to IPv6 addresses */

从代码注释看,默认值是CURL_IPRESOLVE_WHATEVER,默认所有系统的支持IP版本,但是从上面查看Centos结果显示,系统实际不支持IPv6,索性看看代码实现吧。

第二步:grep看一下代码逻辑

/* Check if a limited name resolve has been requested */
switch(conn->ip_version) {
case CURL_IPRESOLVE_V4:
pf = PF_INET;
break;
case CURL_IPRESOLVE_V6:
pf = PF_INET6;
break;
default:
pf = PF_UNSPEC;
break;
}

if((pf != PF_INET) && !Curl_ipv6works())
/* The stack seems to be a non-IPv6 one */
pf = PF_INET;

按默认值的逻辑,代码肯定会走到最后一行,即使用IPv4. 实际上结果并没有,为什么?

Curl_ipv6works()从名称就知道它的功能是什么,看下实现

bool Curl_ipv6works(void)
{
/* the nature of most system is that IPv6 status doesn’t come and go
during a program’s lifetime so we only probe the first time and then we
have the info kept for fast re-use */
static int ipv6_works = -1;
if(-1 == ipv6_works) {
/* probe to see if we have a working IPv6 stack */
curl_socket_t s = socket(PF_INET6, SOCK_DGRAM, 0);
if(s == CURL_SOCKET_BAD)// -1
/* an IPv6 address was requested but we can’t get/use one */
ipv6_works = 0;
else {
ipv6_works = 1;
Curl_closesocket(NULL, s);
}
}
return (ipv6_works>0)?TRUE:FALSE;
}

很清晰,创建一个IPv6的socket套接字,如果成功,就支持,不成功就不支持。

三、验证
写个demo验证一下
socket.php

<?php
$socket = socket_create(AF_INET6, SOCK_DGRAM, 0);//创建失败返回false
if (!$socket) {
die(‘Unable to create AF_UNIX6 socket’);
}
var_dump($socket);
die(‘Yes’);

将代码给运维,在服务器执行一下

$ php socket.php
resource(4) of type (Socket)
Yes

what???

请教了一位运维大佬,回答是,不支持IPv6但是依然可以创建套接字,只是连接不上而已,要彻底关闭,需要明确禁用系统以及网卡的IPv6!

试试看

$ vim /etc/sysctl.conf
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
$ vim /etc/sysconfig/network-script/ifcfg-ens33
IPV6INIT=no

结果显示这两项配置,都已经禁用了IPv6

因此,libcurl判断IPv6始终是生效的,系统对于IPv6的设置并不会影响创建socket套接字。解决方法就是代码中curl的function中直接指定解析IPv4即可

此时,日志文件记录已经超过24小时,拉下来再次看看结果(第四列是超时时间,单位ms)

$ cat all.log | awk -F”\t” ‘{if($4 > 3000) print $0}’ |wc -l
0

指定IPv4之后,问题确实修复了!
四、结论
其实也没结论…
建议:使用CURL时候,没有特殊要求的话,最好还是明确指定为IPv4,防止这种问题发生。

转载请注明:XAMPP中文组官网 » CURL偶发超时时间不受限制?

您必须 登录 才能发表评论!