PHP fsockopen for FAST DNS lookups over UDP
While reading up on gethostbyaddr on PHP.net, I saw a nice idea for using fsockopen to connect over UDP port 53 to any Public DNS server, like Google's 8.8.8.8
, and sending the reverse addr lookup in oh about 100 bytes, then getting the response in oh about 150 bytes! All in less than a second. This would be extremely valuable for use in things like my online header tool because it's faster than any other method. As usual, I went a bit overboard optimizing it to be lean and fast.
It's also a fairly decent example of how to use fsockopen in general.. Fsockopen enables super-hero-like tricks.
PHP fsockopen for DNS lookups
The function has 3 arguments.
- An ip address to lookup.
- A DNS server to query.
- And a timeout in seconds.
Using the 6 fastest DNS servers
This list includes OpenDNS, UltraDNS, Level3, RoadRunner, and of course, Google DNS (see wikipedia for more).
$ip = '208.86.158.195'; foreach ( array('8.8.8.8', '156.154.70.1', '208.67.222.222', '156.154.70.1', '209.244.0.4', '216.146.35.35') as $dns) { echo gethostbyaddr_timeout( $ip, $dns, 1 ); }
The gethostbyaddr source
View the syntax highlighted source.
. function gethostbyaddr_timeout( $ip, $dns, $timeout = 3 ) { // idea from http://www.php.net/manual/en/function.gethostbyaddr.php#46869 // https://www.askapache.com/pub/php/gethostbyaddr.php // random transaction number (for routers etc to get the reply back) $data = rand( 10, 77 ) . "\1\0\0\1\0\0\0\0\0\0"; // octals in the array, keys are strlen of bit $bitso = array("","\1","\2","\3" ); foreach( array_reverse( explode( '.', $ip ) ) as $bit ) { $l=strlen($bit); $data.="{$bitso[$l]}".$bit; } // and the final bit of the request $data .= "\7in-addr\4arpa\0\0\x0C\0\1"; // create UDP socket $errno = $errstr = 0; $fp = fsockopen( "udp://{$dns}", 53, $errno, $errstr, $timeout ); if( ! $fp || ! is_resource( $fp ) ) return $errno; if( function_exists( 'socket_set_timeout' ) ) { socket_set_timeout( $fp, $timeout ); } elseif ( function_exists( 'stream_set_timeout' ) ) { stream_set_timeout( $fp, $timeout ); } // send our request (and store request size so we can cheat later) $requestsize = fwrite( $fp, $data ); $max_rx = $requestsize * 3; $start = time(); $responsesize = 0; while ( $received < $max_rx && ( ( time() - $start ) < $timeout ) && ($buf = fread( $fp, 1 ) ) !== false ) { $responsesize++; $response .= $buf; } //echo "[tx: $requestsize bytes] [rx: {$responsesize} bytes]"; // hope we get a reply if ( is_resource( $fp ) ) fclose( $fp ); // if empty response or bad response, return original ip if ( empty( $response ) || bin2hex( substr( $response, $requestsize + 2, 2 ) ) != '000c' ) return $ip; // set up our variables $host = ''; $len = $loops = 0; // set our pointer at the beginning of the hostname uses the request size from earlier rather than work it out $pos = $requestsize + 12; do { // get segment size $len = unpack( 'c', substr( $response, $pos, 1 ) ); // null terminated string, so length 0 = finished - return the hostname, without the trailing . if ( $len[1] == 0 ) return substr( $host, 0, -1 ); // add segment to our host $host .= substr( $response, $pos + 1, $len[1] ) . '.'; // move pointer on to the next segment $pos += $len[1] + 1; // recursion protection $loops++; } while ( $len[1] != 0 && $loops < 20 ); // return the ip in case return $ip; } ?>
Download and Copy Code
Or download from: gethostbyaddr.txt
function gethostbyaddr_timeout( $ip, $dns, $timeout = 3 ) { // idea from http://www.php.net/manual/en/function.gethostbyaddr.php#46869 // https://www.askapache.com/pub/php/gethostbyaddr.php // random transaction number (for routers etc to get the reply back) $data = rand( 10, 77 ) . "\1\0\0\1\0\0\0\0\0\0"; // octals in the array, keys are strlen of bit $bitso = array("","\1","\2","\3" ); foreach( array_reverse( explode( '.', $ip ) ) as $bit ) { $l=strlen($bit); $data.="{$bitso[$l]}".$bit; } // and the final bit of the request $data .= "\7in-addr\4arpa\0\0\x0C\0\1"; // create UDP socket $errno = $errstr = 0; $fp = fsockopen( "udp://{$dns}", 53, $errno, $errstr, $timeout ); if( ! $fp || ! is_resource( $fp ) ) return $errno; if( function_exists( 'socket_set_timeout' ) ) { socket_set_timeout( $fp, $timeout ); } elseif ( function_exists( 'stream_set_timeout' ) ) { stream_set_timeout( $fp, $timeout ); } // send our request (and store request size so we can cheat later) $requestsize = fwrite( $fp, $data ); $max_rx = $requestsize * 3; $start = time(); $responsesize = 0; while ( $responsesize < $max_rx && ( ( time() - $start ) < $timeout ) && ($buf = fread( $fp, 1 ) ) !== false ) { $responsesize++; $response .= $buf; } // echo "[tx: $requestsize bytes] [rx: {$responsesize} bytes]"; // hope we get a reply if ( is_resource( $fp ) ) fclose( $fp ); // if empty response or bad response, return original ip if ( empty( $response ) || bin2hex( substr( $response, $requestsize + 2, 2 ) ) != '000c' ) return $ip; // set up our variables $host = ''; $len = $loops = 0; // set our pointer at the beginning of the hostname uses the request size from earlier rather than work it out $pos = $requestsize + 12; do { // get segment size $len = unpack( 'c', substr( $response, $pos, 1 ) ); // null terminated string, so length 0 = finished - return the hostname, without the trailing . if ( $len[1] == 0 ) return substr( $host, 0, -1 ); // add segment to our host $host .= substr( $response, $pos + 1, $len[1] ) . '.'; // move pointer on to the next segment $pos += $len[1] + 1; // recursion protection $loops++; } while ( $len[1] != 0 && $loops < 20 ); // return the ip in case return $ip; }
« Boosting Googles PageSpeed Module with TMPFSSeparate favicons for the Frontend and Backend »
Comments