Использование DNS в PHP с примерами кода

Раздел: -> Работа с сетью

Основы работы с DNS в PHP

Какое решение является самым универсальным для получения DNS записей

Функция dns_get_record позволяет запросить любой тип DNS записи (A, AAAA, MX, CNAME, TXT, NS, SOA, SRV и другие). Она возвращает массив ассоциативных массивов с детальным описанием каждой записи.


$domain = 'example.com';
$records = dns_get_record($domain, DNS_ALL);
print_r($records);
  
Array
(
    [0] => Array
        (
            [host] => example.com
            [class] => IN
            [ttl] => 14400
            [type] => A
            [ip] => 93.184.216.34
        )
    [1] => Array
        (
            [host] => example.com
            [class] => IN
            [ttl] => 14400
            [type] => AAAA
            [ipv6] => 2606:2800:220:1:248:1893:25c8:1946
        )
)
  

Второй аргумент - битовая маска типов записей (например, DNS_A, DNS_MX или DNS_ALL для всех).

Типичные проблемы

  • Функция может вернуть пустой массив, если домен не существует или нет записей. Нужно проверять результат.
  • На некоторых хостингах может быть отключена (из-за safe_mode или ограничений). Решение - использовать альтернативные методы (curl к DNS через HTTPS).
  • Тайм-аут по умолчанию - 5 секунд. Можно изменить через ini_set('default_socket_timeout', 10);

Как быстро получить IP-адрес домена (только A запись)

gethostbyname возвращает один IPv4 адрес в виде строки. Подходит для простых проверок.


$ip = gethostbyname('example.com');
echo $ip;
  
93.184.216.34
  

Если требуется массив всех IP адресов для балансировки, используется gethostbynamel.


$ips = gethostbynamel('google.com');
print_r($ips);
  
Array
(
    [0] => 142.250.185.142
    [1] => 142.250.185.138
    [2] => 142.250.185.110
    ...
)
  

Ограничения и ошибки

  • Не возвращает записи AAAA (IPv6). Для IPv6 нужно использовать dns_get_record.
  • При недоступности DNS возвращает исходный домен (неоправданное поведение). Проверяется через filter_var с FILTER_VALIDATE_IP.

Как проверить существование почтовой записи MX

Функция checkdnsrr проверяет, существуют ли записи указанного типа. Возвращает true/false.


$hasMX = checkdnsrr('example.com', 'MX');
if ($hasMX) {
    echo "MX записи найдены";
} else {
    echo "MX записей нет";
}
  

Нюансы

Для получения подробной информации о записях MX (приоритет и адрес) лучше использовать dns_get_record с типом DNS_MX.

Как выполнить обратный DNS запрос (PTR)

Функция gethostbyaddr возвращает доменное имя по IP-адресу.


$hostname = gethostbyaddr('93.184.216.34');
echo $hostname;
  
93.184.216.34
  

Если PTR запись отсутствует, возвращается исходный IP.

Проблемы надежности

Обратные запросы могут быть медленными, если DNS сервер не отвечает. Рекомендуется устанавливать тайм-аут через default_socket_timeout.

Как использовать DNS через HTTPS (DoH) для обхода блокировок

Если встроенные функции заблокированы или нужен сторонний провайдер, применяется curl к API Cloudflare или Google.


$url = 'https://dns.google/resolve?name=example.com&type=A';
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
if (isset($data['Answer'])) {
    foreach ($data['Answer'] as $answer) {
        echo $answer['data'] . "\n";
    }
}
  
93.184.216.34
  

Возможные трудности

  • Требуется расширение curl и разрешение на внешние соединения.
  • У разных провайдеров разный формат ответа. Необходимо адаптировать код под конкретный API.

Расширенные примеры работы с DNS в PHP

Пример 1. Получение всех типов записей для домена с обработкой ошибок

Пример

function getDnsRecordsSafe($domain, $timeout = 5) {
    ini_set('default_socket_timeout', $timeout);
    $records = @dns_get_record($domain, DNS_A | DNS_AAAA | DNS_MX | DNS_CNAME | DNS_TXT | DNS_NS | DNS_SOA | DNS_SRV);
    if ($records === false) {
        throw new Exception("DNS запрос не удался для домена: $domain");
    }
    return $records;
}

try {
    $result = getDnsRecordsSafe('github.com');
    foreach ($result as $rec) {
        $type = $rec['type'];
        $host = $rec['host'];
        $data = '';
        switch ($type) {
            case 'A':
                $data = $rec['ip'];
                break;
            case 'AAAA':
                $data = $rec['ipv6'];
                break;
            case 'MX':
                $data = $rec['target'] . ' (priority ' . $rec['pri'] . ')';
                break;
            case 'CNAME':
                $data = $rec['target'];
                break;
            case 'TXT':
                $data = $rec['txt'];
                break;
            case 'NS':
                $data = $rec['target'];
                break;
            default:
                $data = json_encode($rec);
        }
        echo "$type: $host -> $data\n";
    }
} catch (Exception $e) {
    echo "Ошибка: " . $e->getMessage();
}
A: github.com -> 20.27.177.113
AAAA: github.com -> (пусто, нет записи)
MX: github.com -> alt1.aspmx.l.google.com (priority 1)
TXT: github.com -> "v=spf1 include:spf.github.com ~all"
NS: github.com -> ns1.p16.dynect.net
...

Пример 2. Кэширование DNS запросов с помощью файла или Memcached

Пример

function getDnsWithCache($domain, $type = DNS_A, $cacheFile = '/tmp/dns_cache.json', $ttl = 86400) {
    $cache = [];
    if (file_exists($cacheFile)) {
        $cache = json_decode(file_get_contents($cacheFile), true);
    }
    $key = md5($domain . $type);
    if (isset($cache[$key]) && (time() - $cache[$key]['time'] < $ttl)) {
        return $cache[$key]['data'];
    }
    $records = dns_get_record($domain, $type);
    $cache[$key] = ['time' => time(), 'data' => $records];
    file_put_contents($cacheFile, json_encode($cache));
    return $records;
}

$mx = getDnsWithCache('yandex.ru', DNS_MX);
print_r($mx);
Array
(
    [0] => Array
        (
            [host] => yandex.ru
            [class] => IN
            [ttl] => 3600
            [type] => MX
            [pri] => 10
            [target] => mx.yandex.ru
        )
)

Пример 3. Рекурсивный поиск CNAME до конечного A/AAAA

Пример

function resolveCanonical($domain, $depth = 10) {
    if ($depth <= 0) return $domain;
    $rec = dns_get_record($domain, DNS_CNAME);
    if (!empty($rec) && $rec[0]['type'] == 'CNAME') {
        return resolveCanonical($rec[0]['target'], $depth - 1);
    }
    return $domain;
}

$final = resolveCanonical('www.google.com');
echo $final; // скорее всего google.com
google.com

Пример 4. Использование DNS over HTTPS (DoH) с несколькими провайдерами

Пример

function dohQuery($domain, $type = 'A', $provider = 'cloudflare') {
    $providers = [
        'cloudflare' => 'https://cloudflare-dns.com/dns-query?name=' . $domain . '&type=' . $type,
        'google' => 'https://dns.google/resolve?name=' . $domain . '&type=' . $type,
    ];
    $url = $providers[$provider] ?? $providers['cloudflare'];
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ['Accept: application/dns-json'],
        CURLOPT_TIMEOUT => 5,
    ]);
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($httpCode !== 200) return null;
    $data = json_decode($response, true);
    $answers = [];
    if (isset($data['Answer'])) {
        foreach ($data['Answer'] as $ans) {
            if ($ans['type'] === 1 && $type === 'A') $answers[] = $ans['data'];
            if ($ans['type'] === 28 && $type === 'AAAA') $answers[] = $ans['data'];
        }
    }
    return $answers;
}

$ipv4 = dohQuery('example.com', 'A', 'google');
print_r($ipv4);
Array
(
    [0] => 93.184.216.34
)

Пример 5. Получение SRV записей для SIP или XMPP

Пример

$domain = '_sip._tcp.example.com';
$records = dns_get_record($domain, DNS_SRV);
if ($records) {
    foreach ($records as $srv) {
        echo "Priority: {$srv['pri']}, Weight: {$srv['weight']}, Port: {$srv['port']}, Target: {$srv['target']}\n";
    }
} else {
    echo "SRV записи не найдены";
}
Priority: 10, Weight: 60, Port: 5060, Target: sip.example.com
Priority: 20, Weight: 40, Port: 5061, Target: sip2.example.com

Пример 6. Обработка ошибок при тайм-ауте с пользовательским исключением

Пример

function dnsWithTimeout($domain, $timeout = 2) {
    $originalTimeout = ini_get('default_socket_timeout');
    ini_set('default_socket_timeout', $timeout);
    $records = @dns_get_record($domain, DNS_A);
    ini_set('default_socket_timeout', $originalTimeout);
    if ($records === false || empty($records)) {
        throw new RuntimeException("DNS не ответил за $timeout секунд или запись отсутствует");
    }
    return $records;
}

try {
    $a = dnsWithTimeout('nonexistent-domain-xyz.test', 1);
} catch (RuntimeException $e) {
    echo "Проблема: " . $e->getMessage();
}
Проблема: DNS не ответил за 1 секунд или запись отсутствует

Работа с DNS в PHP - comments

En
Php dns (php)