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.' ) );