损坏的 DNS 服务器的问题给我带来了麻烦,因为我有一个用户统计页面,需要进行大约 20 次反向 DNS 查询,即使其中 5/6 个查询都坏了,也会导致页面加载速度大幅减慢。所以我编写了一个函数,它使用 UDP 套接字直接与 DNS 服务器通信 (而不是通过正常的 gethostbyaddr 函数),这使我能够设置超时。
唯一的要求是您的 DNS 服务器必须能够进行递归查询,它不会被告知去其他 DNS 服务器...当然,您需要知道您的 DNS 服务器的 IP 地址 :-)
<?
function gethostbyaddr_timeout($ip, $dns, $timeout=1000)
{
// 随机事务编号 (供路由器等获取回复)
$data = rand(0, 99);
// 将其缩短为 2 个字节
$data = substr($data, 0, 2);
// 请求头
$data .= "\1\0\0\1\0\0\0\0\0\0";
// 拆分 IP
$bits = explode(".", $ip);
// 错误检查
if (count($bits) != 4) return "ERROR";
// 可能有更好的方法来做这一部分...
// 循环遍历每个段
for ($x=3; $x>=0; $x--)
{
// 需要一个字节来指示每个请求段的长度
switch (strlen($bits[$x]))
{
case 1: // 1 字节长的段
$data .= "\1"; break;
case 2: // 2 字节长的段
$data .= "\2"; break;
case 3: // 3 字节长的段
$data .= "\3"; break;
default: // 段太大,无效 IP
return "INVALID";
}
// 以及段本身
$data .= $bits[$x];
}
// 以及请求的最后部分
$data .= "\7in-addr\4arpa\0\0\x0C\0\1";
// 创建 UDP 套接字
$handle = @fsockopen("udp://$dns", 53);
// 发送我们的请求 (并存储请求大小以便稍后作弊)
$requestsize=@fwrite($handle, $data);
@socket_set_timeout($handle, $timeout - $timeout%1000, $timeout%1000);
// 希望我们能得到回复
$response = @fread($handle, 1000);
@fclose($handle);
if ($response == "")
return $ip;
// 查找响应类型
$type = @unpack("s", substr($response, $requestsize+2));
if ($type[1] == 0x0C00) // 答案
{
// 设置我们的变量
$host="";
$len = 0;
// 将我们的指针置于主机名的开头
// 使用早期的请求大小而不是计算出来
$position=$requestsize+12;
// 重构主机名
do
{
// 获取段大小
$len = unpack("c", substr($response, $position));
// 以空字符结尾的字符串,所以长度为 0 表示结束
if ($len[1] == 0)
// 返回主机名,不带尾部的点
return substr($host, 0, strlen($host) -1);
// 将段添加到我们的主机
$host .= substr($response, $position+1, $len[1]) . ".";
// 将指针移至下一段
$position += $len[1] + 1;
}
while ($len != 0);
// 错误 - 返回我们构建的主机名 (不带尾部的点)
return $ip;
}
return $ip;
}
?>
这可以扩展很多并改进,但它有效,我看到很多人尝试了各种方法来实现类似的功能,所以我决定在这里发布它。在大多数服务器上,它也应该比其他方法 (如调用 nslookup) 更有效,因为它不需要运行外部程序
注意:我更擅长 C 而不是 PHP,所以如果有些事情没有用*推荐*的方式完成,请忽略它 :-)