损坏的 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,所以如果有什么没有以 *推荐* 的方式完成,请忽略它 :-)