FREE THOUGHT · FREE SOFTWARE · FREE WORLD

Home  »  PHP  »  PHP fsockopen for FAST DNS lookups over UDP

by 6 comments

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);
}

The gethostbyaddr source

View the syntax highlighted source.

# @ http://www.askapache.com/php/php-fsockopen-dns-udp.html
# 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 ) {
    
// idea from http://www.php.net/manual/en/function.gethostbyaddr.php#46869
    // http://www.askapache.com/pub/php/gethostbyaddr.php
    
    // random transaction number (for routers etc to get the reply back)
    
$data rand1077 ) . "\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_reverseexplode'.'$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) ) !== 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 ) || bin2hexsubstr$response$requestsize 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) );
        
        
// null terminated string, so length 0 = finished - return the hostname, without the trailing .
        
if ( $len[1] == )
            return 
substr$host0, -);
            
        
// 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] != && $loops 20 );
    
    
// return the ip in case 
    
return $ip;
}

Download and Copy Code

Or download from: gethostbyaddr.txt

function gethostbyaddr_timeout$ip$dns$timeout ) {
    
// idea from http://www.php.net/manual/en/function.gethostbyaddr.php#46869
    // http://www.askapache.com/pub/php/gethostbyaddr.php
    
    // random transaction number (for routers etc to get the reply back)
    
$data rand1077 ) . "\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_reverseexplode'.'$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) ) !== 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 ) || bin2hexsubstr$response$requestsize 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) );
        
        
// null terminated string, so length 0 = finished - return the hostname, without the trailing .
        
if ( $len[1] == )
            return 
substr$host0, -);
            
        
// 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] != && $loops 20 );
    
    
// return the ip in case 
    
return $ip;
}

Tags

January 28th, 2014

Comments Welcome

  • pantonini

    Nice improvement but it bugs with this address : 217.195.30.102

  • pantonini

    Nice improvement but it bugs with this address : 217.195.30.102

  • pantonini

    I fixed the problem by adding a test for an STX character at position requestsize +12 :

    if ( empty( $response ) || bin2hex( substr( $response, $requestsize + 2, 2 ) ) != '000c' || bin2hex( substr( $response, $requestsize + 12, 1 ) ) != '02' ) {

    return $ip;

    }

  • pantonini

    I fixed the problem by adding a test for an STX character at position requestsize +12 :

    if ( empty( $response ) || bin2hex( substr( $response, $requestsize + 2, 2 ) ) != '000c' || bin2hex( substr( $response, $requestsize + 12, 1 ) ) != '02' ) {

    return $ip;

    }

  • pantonini

    My previous fix was wrong.

    After the test " if empty response or bad response, return original ip ", you have to add a test to verify that the last character of the response is a 0 :

    // Verify the last character of response is 0
    $responseLength = hexdec(bin2hex(substr( $response, $requestsize + 11, 1 )));
    if ( bin2hex(substr( $response, $requestsize + $responseLength + 11, 1 )) != '00' ) {
    return $ip;
    }

  • pantonini

    My previous fix was wrong.

    After the test " if empty response or bad response, return original ip ", you have to add a test to verify that the last character of the response is a 0 :

    // Verify the last character of response is 0
    $responseLength = hexdec(bin2hex(substr( $response, $requestsize + 11, 1 )));
    if ( bin2hex(substr( $response, $requestsize + $responseLength + 11, 1 )) != '00' ) {
    return $ip;
    }

Popular Articles
My Online Tools

Related Articles
Newest Posts
Twitter



Hacking and Hackers

The use of "hacker" to mean "security breaker" is a confusion on the part of the mass media. We hackers refuse to recognize that meaning, and continue using the word to mean someone who loves to program, someone who enjoys playful cleverness, or the combination of the two. See my article, On Hacking.
-- Richard M. Stallman






[hide]

It's very simple - you read the protocol and write the code. -Bill Joy

Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution 3.0 License, just credit with a link.
This site is not supported or endorsed by The Apache Software Foundation (ASF). All software and documentation produced by The ASF is licensed. "Apache" is a trademark of The ASF. NCSA HTTPd.
UNIX ® is a registered Trademark of The Open Group. POSIX ® is a registered Trademark of The IEEE.

| Google+ | askapache

Site Map | Contact Webmaster | License and Disclaimer | Terms of Service

↑ TOPMain