Verificare la configurazione del record PTR di un dominio con PHP
Il record PTR (Pointer Record) è uno dei tasselli più importanti, e spesso più trascurati, della configurazione DNS di un dominio. Mentre i record A e AAAA mappano un nome di dominio su un indirizzo IP, il record PTR fa esattamente l'opposto: associa un indirizzo IP a un nome di dominio. Questa risoluzione inversa, comunemente chiamata reverse DNS lookup o rDNS, è fondamentale in numerosi contesti, dalla diagnostica di rete alla reputazione dei server di posta elettronica.
In questo articolo vedremo cos'è realmente un record PTR, perché è importante verificarlo e come costruire una piccola libreria in PHP per controllarne la configurazione corretta, sfruttando le funzioni native del linguaggio dedicate alle interrogazioni DNS.
Cos'è un record PTR e perché è importante
Un record PTR risiede in una zona DNS speciale chiamata in-addr.arpa per IPv4 e ip6.arpa per IPv6. Quando un servizio remoto vuole sapere a quale hostname appartiene un certo indirizzo IP, interroga questa zona ottenendo come risposta un nome di dominio fully qualified (FQDN).
La presenza e la correttezza di un record PTR è particolarmente critica per i server SMTP. Molti provider di posta elettronica rifiutano o classificano come spam i messaggi provenienti da server il cui IP non ha un record PTR valido, oppure il cui PTR non è coerente con il record A corrispondente. Questa coerenza, detta Forward-Confirmed reverse DNS (FCrDNS), si ottiene quando il PTR di un IP punta a un hostname il cui record A risolve nuovamente allo stesso IP di partenza.
Verificare programmaticamente la configurazione di un PTR consente quindi di:
- Diagnosticare problemi di consegna delle email in uscita.
- Validare la configurazione di un mail server appena messo in produzione.
- Eseguire controlli di sicurezza su connessioni in entrata.
- Implementare sistemi di logging che mostrino hostname leggibili invece di soli indirizzi IP.
Le funzioni DNS di PHP
PHP offre nativamente diverse funzioni per interrogare il DNS, raccolte nell'estensione standard. Le più rilevanti per il nostro scopo sono gethostbyaddr(), che esegue una risoluzione inversa restituendo l'hostname associato a un IP, gethostbyname(), che esegue il percorso opposto, e dns_get_record(), che permette interrogazioni più granulari specificando il tipo di record desiderato.
La funzione gethostbyaddr() è il punto di partenza naturale per leggere un PTR. Accetta in input una stringa contenente un indirizzo IPv4 o IPv6 e restituisce l'hostname corrispondente, oppure l'IP stesso se nessun record PTR è configurato.
<?php
$ipAddress = '8.8.8.8';
$hostname = gethostbyaddr($ipAddress);
// Stampa l'hostname risolto oppure l'IP se il PTR non esiste
echo $hostname;
Il limite di gethostbyaddr() è che, in caso di assenza di un record PTR, non restituisce un valore facilmente distinguibile da un esito positivo: ritorna semplicemente la stringa dell'IP originale. Per questo motivo è buona pratica confrontare il risultato con l'input.
Una prima verifica essenziale
Costruiamo una funzione che indichi in modo chiaro se un determinato IP ha un record PTR configurato.
<?php
function hasPtrRecord(string $ipAddress): bool
{
// Verifica preliminare della validità dell'indirizzo IP
if (!filter_var($ipAddress, FILTER_VALIDATE_IP)) {
return false;
}
$hostname = gethostbyaddr($ipAddress);
// Se la risoluzione fallisce o restituisce l'IP stesso, il PTR non è configurato
return $hostname !== false && $hostname !== $ipAddress;
}
var_dump(hasPtrRecord('8.8.8.8')); // true
var_dump(hasPtrRecord('192.0.2.123')); // false (range riservato per documentazione)
La validazione preliminare con filter_var() e il flag FILTER_VALIDATE_IP ci protegge da input malformati. Il confronto stretto con l'IP di partenza ci permette di intercettare il caso in cui gethostbyaddr() non riesca a trovare un record PTR.
Verifica della coerenza Forward-Confirmed
Sapere che un PTR esiste è solo metà del lavoro. Per essere veramente utile, soprattutto in ambito email, il PTR deve essere coerente con il record A diretto. Questo significa che, dato un IP, il suo PTR deve restituire un hostname che a sua volta, risolto come record A, restituisca lo stesso IP iniziale.
<?php
function isForwardConfirmed(string $ipAddress): bool
{
if (!filter_var($ipAddress, FILTER_VALIDATE_IP)) {
return false;
}
// Esegue la risoluzione inversa: IP -> hostname
$hostname = gethostbyaddr($ipAddress);
if ($hostname === false || $hostname === $ipAddress) {
return false;
}
// Esegue la risoluzione diretta: hostname -> IP
$resolvedIp = gethostbyname($hostname);
// La coerenza FCrDNS è confermata solo se gli IP coincidono
return $resolvedIp === $ipAddress;
}
var_dump(isForwardConfirmed('8.8.8.8'));
Questa funzione effettua il classico controllo bidirezionale richiesto dai server di posta più severi. Il suo limite, però, è che gethostbyname() gestisce solo IPv4. Per supportare anche IPv6 dobbiamo affidarci a dns_get_record().
Una verifica completa con dns_get_record
La funzione dns_get_record() consente di richiedere esplicitamente record di tipo A per IPv4 e AAAA per IPv6, restituendo un array associativo con tutti i dati pertinenti. Combinandola con un controllo sulla famiglia di indirizzi possiamo costruire una verifica robusta e moderna.
<?php
function verifyPtrConfiguration(string $ipAddress): array
{
$result = [
'ip' => $ipAddress,
'valid_ip' => false,
'has_ptr' => false,
'hostname' => null,
'forward_confirmed' => false,
'forward_ips' => [],
];
// Validazione iniziale dell'indirizzo IP
if (!filter_var($ipAddress, FILTER_VALIDATE_IP)) {
return $result;
}
$result['valid_ip'] = true;
// Risoluzione inversa per ottenere l'hostname
$hostname = gethostbyaddr($ipAddress);
if ($hostname === false || $hostname === $ipAddress) {
return $result;
}
$result['has_ptr'] = true;
$result['hostname'] = $hostname;
// Determina il tipo di record da interrogare in base alla famiglia IP
$isIpv6 = filter_var($ipAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
$recordType = $isIpv6 ? DNS_AAAA : DNS_A;
$ipField = $isIpv6 ? 'ipv6' : 'ip';
// Interroga il DNS per ottenere tutti gli IP associati all'hostname
$records = dns_get_record($hostname, $recordType);
if ($records === false || count($records) === 0) {
return $result;
}
// Estrae solo gli indirizzi IP dai record restituiti
$forwardIps = array_column($records, $ipField);
$result['forward_ips'] = $forwardIps;
// La coerenza è confermata se l'IP iniziale è tra quelli risolti
$result['forward_confirmed'] = in_array($ipAddress, $forwardIps, true);
return $result;
}
$report = verifyPtrConfiguration('8.8.8.8');
print_r($report);
Il valore restituito è un array strutturato che descrive ogni passaggio della verifica: la validità dell'IP, la presenza del PTR, l'hostname ottenuto, gli IP a cui l'hostname risolve e l'esito finale del controllo FCrDNS. Una struttura di questo tipo è particolarmente utile in contesti di reportistica o in API REST che espongono lo stato di configurazione DNS di un dominio.
Gestione degli errori e dei timeout
Le interrogazioni DNS sono operazioni di rete e, come tali, possono fallire o impiegare tempi imprevedibili. È buona norma gestire esplicitamente i possibili scenari di errore, soprattutto in script che girano in produzione. PHP non offre un timeout nativo configurabile per gethostbyaddr(), ma possiamo controllare meglio il comportamento usando un pattern basato su set_error_handler() per intercettare i warning generati da dns_get_record().
<?php
function safeDnsLookup(string $hostname, int $type): array
{
$records = [];
// Imposta un gestore temporaneo per gli errori
set_error_handler(function (int $severity, string $message): bool {
// Sopprime i warning DNS evitando l'interruzione del flusso
return true;
});
try {
$result = dns_get_record($hostname, $type);
if (is_array($result)) {
$records = $result;
}
} finally {
// Ripristina sempre il gestore originale
restore_error_handler();
}
return $records;
}
Questo pattern garantisce che eventuali warning generati da query DNS fallite non inquinino l'output dello script o non interrompano l'esecuzione in ambienti dove gli errori vengono trattati come eccezioni. Il blocco finally assicura il ripristino del gestore originale anche in caso di eccezioni.
Verifica multipla su una lista di indirizzi
In contesti operativi capita di dover verificare la configurazione PTR di più server contemporaneamente, ad esempio l'intero pool di un provider di posta o tutti i nodi di un cluster. Possiamo costruire una funzione che accetti un array di IP e restituisca un report consolidato.
<?php
function bulkVerifyPtr(array $ipAddresses): array
{
$reports = [];
foreach ($ipAddresses as $ip) {
$reports[] = verifyPtrConfiguration($ip);
}
return $reports;
}
function summarizeReports(array $reports): array
{
$total = count($reports);
$withPtr = 0;
$forwardConfirmed = 0;
foreach ($reports as $report) {
if ($report['has_ptr']) {
$withPtr++;
}
if ($report['forward_confirmed']) {
$forwardConfirmed++;
}
}
return [
'total' => $total,
'with_ptr' => $withPtr,
'forward_confirmed' => $forwardConfirmed,
'ptr_ratio' => $total > 0 ? round($withPtr / $total, 2) : 0,
'fcrdns_ratio' => $total > 0 ? round($forwardConfirmed / $total, 2) : 0,
];
}
$ips = ['8.8.8.8', '1.1.1.1', '208.67.222.222'];
$reports = bulkVerifyPtr($ips);
$summary = summarizeReports($reports);
print_r($summary);
Le due funzioni sono pensate per essere componibili: bulkVerifyPtr() produce il dettaglio per ogni IP, mentre summarizeReports() ne calcola le metriche aggregate. Questo approccio facilita l'integrazione in dashboard di monitoraggio o in sistemi di alerting che debbano segnalare anomalie nella configurazione DNS dell'infrastruttura.
Considerazioni sulle performance
Le query DNS, anche quando vanno a buon fine, introducono latenza. Una verifica completa per un singolo IP può richiedere centinaia di millisecondi, e questo tempo si moltiplica linearmente quando si lavora su liste di indirizzi. In contesti dove le performance sono critiche conviene introdurre meccanismi di caching, ad esempio memorizzando i risultati in Redis o Memcached con un TTL coerente con quello dei record DNS interrogati.
Inoltre, su sistemi Linux è possibile sfruttare il resolver locale del sistema operativo, che spesso include già una cache. Verificare la configurazione di /etc/nsswitch.conf e dei servizi come nscd o systemd-resolved può migliorare sensibilmente i tempi di risposta delle funzioni DNS di PHP, che internamente delegano la risoluzione al resolver di sistema.
Conclusioni
La verifica programmatica del record PTR è un'attività che richiede poche righe di codice ma che apre la porta a un controllo molto più profondo della salute di un'infrastruttura. Combinando le funzioni DNS native di PHP con una validazione attenta degli input e una gestione robusta degli errori, è possibile costruire strumenti di diagnostica affidabili e riutilizzabili.
Il pattern presentato, basato su una funzione di verifica completa che ritorna un report strutturato, si presta naturalmente a essere esteso ulteriormente: si possono aggiungere controlli sui record MX, sulle policy SPF e DMARC, oppure integrare il tutto in una libreria di health check da eseguire periodicamente sui propri server. Il risultato è una maggiore consapevolezza della propria configurazione DNS e, di conseguenza, un'infrastruttura più solida e meno soggetta a problemi sottili come quelli legati alla deliverability delle email.