損壞的 DNS 伺服器問題一直困擾著我,因為我有一個使用者統計頁面,需要執行大約 20 次反向 DNS 查詢,即使其中多達 5/6 個損壞,也會導致頁面載入的巨大延遲。因此,我編寫了一個函數,使用 UDP socket 直接與 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 socket
$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));
// 以 null 終止的字串,因此長度 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,所以如果任何事情沒有以*建議的*方式完成,請忽略它 :-)