Raw socket DNS request in PHP
Solution 1:
Mostly, what you've failed to spot is that the C code uses nine separate bitfields that pack the DNS header flags into a singe 16-bit field, whereas you're trying to append 9 separate bytes.
You should just add pack('n', 0x0100)
after the random Query ID, so as to set the RD
(recursion desired) bit.
Also, you should only append a single \0
after the domain name, to represent the trailing "root" label, or alternatively ensure that every domain name you supply already has a trailing dot such that the explode
function does that work for you. This code works:
$domain = 'www.google.com.';
$dns = '8.8.8.8';
$timeout = 2;
$data = pack('n6', rand(10, 77), 0x0100, 1, 0, 0, 0);
foreach (explode('.', $domain) as $bit) {
$l = strlen($bit);
$data .= chr($l) . $bit;
}
$data .= pack('n2', 2, 1); // QTYPE=NS, QCLASS=IN
Don't forget to refer to RFC 1035 to learn how to decode the response properly!
Solution 2:
I needed a code to check if a certain DNS server was delegating the DNS to another server, I've started of from the example above, my final code is:
function has_ns_records( $domain, $dns = 'dns1.nic.blog' ) {
$timeout = 2;
// Create a question packet
$data = pack( 'n6', rand(10, 77), 0x0100, 1, 0, 0, 0 );
foreach ( explode('.', $domain ) as $part ) {
$length = strlen( $part );
$data .= chr( $length ) . $part;
}
$data .= pack( 'n2' , 2, 1 ); // QTYPE=NS, QCLASS=IN
$errno = $errstr = 0;
$fp = fsockopen( 'udp://' . $dns, 53, $errno, $errstr, $timeout );
if (!$fp || !is_resource($fp)) return $errno;
socket_set_timeout( $fp, $timeout );
fwrite( $fp, $data );
$response_data = fread( $fp, 8192 );
fclose( $fp );
// read answer header
$ans_header = unpack( "nid/nspec/nqdcount/nancount/nnscount/narcount", substr( $response_data, 0, 12 ) );
// skip question part
$offset = strlen( $domain ) + 4 + 2 + 1; // 4 => QTYPE + QCLASS, 2 => len, 1 => null terminator
$record_header = unpack("ntype/nclass/Nttl/nlength", substr( $response_data, 12 + $offset, 10 ) );
// Expect type NS and class IN, when domain not exist it returns SOA (6)
return $record_header[ 'type' ] === 2 && $record_header[ 'class' ] === 1;
}
var_dump( has_ns_records( 'get.blog.' ) );