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.
# @ https://www.askapache.com/php/php-fsockopen-dns-udp/
# Copyright (C) 2013 Free Software Foundation, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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