Search code examples
phpapachenginxgeolocationip-address

PHP visitorIP() returning localhost address


I'm using the php code below to get the user's IP address in order to set their location. It works in our QA environment but in our prod environment, it always returns the IP as 127.0.0.1. I guess there must be some different Apache/Nginx (we use both) configuration between the two environments but I don't know what configuration would have an effect on this(?). The only other difference between the two environments would be that on our prod environment (hosted in the RackSpace cloud), traffic would first hit a load balancer... but I don't see why this would alter the IP to localhost so I don't believe that's the case - correct me if I'm wrong.

<?php 

$userIp = visitorIP();
$ipquad = explode(".", $userIp);
$ipnum = 16777216 * $ipquad[0] + 65536 * $ipquad[1] + 256 * $ipquad[2] + $ipquad[3];
var_dump('IP:- '.$userIp);
var_dump($ipquad);
var_dump('ipnum:- '.$ipnum);
die;

function visitorIP() {

    $ip = $_SERVER['REMOTE_ADDR'];

    if(isset($_SERVER['HTTP_X_FORWARDED_FOR']) && urlIsPublic($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    } 

    /* Check for Akamai custom header */
    if(isset($_SERVER['HTTP_TRUE_CLIENT_IP'])){
        $ip = $_SERVER['HTTP_TRUE_CLIENT_IP'];
    }   


    if (!(urlIsPublic(trim($ip)))) {
        if(defined('IP')) {
            return IP;
        }
    }

    return trim($ip);
}

function urlIsPublic($ip) {
    if(filter_var($ip,
                           FILTER_VALIDATE_IP,
                           FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)
                !== false) {
        return true;
    }
    return false;

} 

?>

The output of var_dump($_SERVER) is below...

array(29) { ["APPLICATION_ENV"]=> string(10) "production" ["HTTP_HOST"]=> string(19) "www.example.com" ["HTTP_X_FORWARDED_FOR"]=> string(32) "<MY_PUBLIC_IP>, 192.168.200.154" ["HTTP_CONNECTION"]=> string(5) "close" ["HTTP_CACHE_CONTROL"]=> string(9) "max-age=0" ["HTTP_UPGRADE_INSECURE_REQUESTS"]=> string(1) "1" ["HTTP_USER_AGENT"]=> string(109) "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36" ["HTTP_ACCEPT"]=> string(74) "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" ["HTTP_ACCEPT_ENCODING"]=> string(19) "gzip, deflate, sdch" ["HTTP_ACCEPT_LANGUAGE"]=> string(26) "en-GB,en-US;q=0.8,en;q=0.6" ["PATH"]=> string(29) "/sbin:/usr/sbin:/bin:/usr/bin" ["SERVER_SIGNATURE"]=> string(80) "
Apache/2.2.15 (CentOS) Server at www.example.com Port 80
" ["SERVER_SOFTWARE"]=> string(22) "Apache/2.2.15 (CentOS)" ["SERVER_NAME"]=> string(19) "www.example.com" ["SERVER_ADDR"]=> string(9) "127.0.0.1" ["SERVER_PORT"]=> string(2) "80" ["REMOTE_ADDR"]=> string(9) "127.0.0.1" ["DOCUMENT_ROOT"]=> string(37) "/var/www/vhosts/example/httpdocs" ["SERVER_ADMIN"]=> string(14) "root@localhost" ["SCRIPT_FILENAME"]=> string(48) "/var/www/vhosts/example/httpdocs/ipinfo.php" ["REMOTE_PORT"]=> string(5) "45532" ["GATEWAY_INTERFACE"]=> string(7) "CGI/1.1" ["SERVER_PROTOCOL"]=> string(8) "HTTP/1.0" ["REQUEST_METHOD"]=> string(3) "GET" ["QUERY_STRING"]=> string(0) "" ["REQUEST_URI"]=> string(11) "/ipinfo.php" ["SCRIPT_NAME"]=> string(11) "/ipinfo.php" ["PHP_SELF"]=> string(11) "/ipinfo.php" ["REQUEST_TIME"]=> int(1493132187) }

Solution

  • Since it's a comma separated list, you need to check each individual IP:

    function urlIsPublic($ip_list) {
        $ips = explode(',', $ip_list);
        foreach($ips as $ip) {
            $ip = trim($ip);
            if(filter_var($ip,
                    FILTER_VALIDATE_IP,
                    FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)
                !== false) {
                return $ip;
            }
        }
        return false;
    }
    

    You can then use it like this, and check if $ip is false later on:

    if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ip = urlIsPublic($_SERVER['HTTP_X_FORWARDED_FOR']);
    }