Create GeoIP Badge Using PHP Image Processing And GD

When making the badges I always use javascript. This tutorial shows you how to use the GD library to dynamically create simple badges on your site.

Basically, image generation is done in three steps:

  • allocating an image
  • drawing into the allocated space
  • releasing the allocated data in picture format to the browser

[cfields]badgegd[/cfields]

As usual, obtaining client Ip Address simply by calling this php predefined variable, this time we will also trying to support for IPv6. One drawback, i still don’t know how to resize canvas to be fit with text if overflown, dynamically. Ie: the long IPv6 address.

<?php echo $_SERVER["REMOTE_ADDR"]; ?>

Proxy Blues

About proxy, I have not yet found a usefull methods on how to determine whether the client is behind a proxy or directly connected to our website. The problem with “HTTP_*” headers is that they can easily be faked by the client. And proxy itself can easily turn those things off.

The X-Forwarded-For (XFF) HTTP header field is a de facto standard for identifying the originating IP address of a client connecting to a web server through an HTTP proxy or load balancer. This is a non-RFC-standard request field which was introduced by the Squid caching proxy server’s developers.

In a perfect world, we can obtain client and proxy ip address through HTTP_X_FORWARDED_FOR, The general format of the X-Forwarded-For field is:

X-Forwarded-For: client, proxy1, proxy2

Without the use of X-Forwarded-For, any connection through the proxy would reveal only the originating IP address of the proxy server, that’s what we called proxy anonymizer. it makes the detection and prevention of abusive accesses significantly harder than if the originating IP address was available.

This simple program should be able to detect whether client behind proxy or not.

<?php
$ip = explode(",", $_SERVER['HTTP_X_FORWARDED_FOR']);

echo "REMOTE_ADDR : " . $_SERVER['REMOTE_ADDR'] . "<br>\n";
echo "HTTP_X_REAL_IP : " . $_SERVER['HTTP_X_REAL_IP'] . "<br>\n";
echo "HTTP_X_FORWARDED_FOR Client : " . $ip[0] . "<br>\n";
echo "HTTP_X_FORWARDED_FOR Proxy  : " . $ip[1] . "<br>\n";
?>

This program will display the results like this when using a proxy that turn on the X-Forwarded-For information.

REMOTE_ADDR : 192.168.200.112
HTTP_X_REAL_IP : 192.168.200.112
HTTP_X_FORWARDED_FOR Client : 192.168.200.100
HTTP_X_FORWARDED_FOR Proxy : 192.168.200.112

What about these headers?

  • HTTP_VIA
  • HTTP_FORWARDED_FOR
  • HTTP_X_FORWARDED
  • HTTP_FORWARDED
  • HTTP_CLIENT_IP
  • HTTP_FORWARDED_FOR_IP
  • VIA
  • X_FORWARDED_FOR
  • FORWARDED_FOR
  • X_FORWARDED
  • FORWARDED
  • CLIENT_IP
  • FORWARDED_FOR_IP
  • HTTP_PROXY_CONNECTION

Using this code i’ve found that if i’m dealing with sane proxy(i’ve setup proxy using mikrotik) i’ll get a good result.

<?php
$proxy_headers = array(
        'HTTP_VIA',
        'HTTP_X_FORWARDED_FOR',
        'HTTP_FORWARDED_FOR',
        'HTTP_X_FORWARDED',
        'HTTP_FORWARDED',
        'HTTP_CLIENT_IP',
        'HTTP_FORWARDED_FOR_IP',
        'VIA',
        'X_FORWARDED_FOR',
        'FORWARDED_FOR',
        'X_FORWARDED',
        'FORWARDED',
        'CLIENT_IP',
        'FORWARDED_FOR_IP',
        'HTTP_PROXY_CONNECTION'
);

for ($i=0;$i<count($proxy_headers);$i++) {
        echo $proxy_headers[$i] . " = " . $_SERVER[$proxy_headers[$i]] . "<br>\n";
}
?>

Test Result

HTTP_VIA = 1.1 192.168.200.112 (Mikrotik HttpProxy)
HTTP_X_FORWARDED_FOR = 192.168.200.100, 192.168.200.112
HTTP_FORWARDED_FOR =
HTTP_X_FORWARDED =
HTTP_FORWARDED =
HTTP_CLIENT_IP =
HTTP_FORWARDED_FOR_IP =
VIA =
X_FORWARDED_FOR =
FORWARDED_FOR =
X_FORWARDED =
FORWARDED =
CLIENT_IP =
FORWARDED_FOR_IP =
HTTP_PROXY_CONNECTION =

But, are there any proxies out there advertise client’s ip in their HTTP_X_FORWARDED_FOR?. even corporate proxies, hide their client information for security reasons.

OK, back to the topic about badge creation.

Files required:

  • GeoIP.dat
  • GeoIPv6.dat
  • GeoLiteCity.dat
  • GeoLiteCityv6.dat
  • geoip.inc
  • geoipcity.inc
  • geoipregionvars.php

These files can be downloaded from www.maxmind.com

$ mkdir geoip-api && cd geoip-api
$ wget http://geolite.maxmind.com/download/geoip/api/php/geoip.inc
$ wget http://geolite.maxmind.com/download/geoip/api/php/geoipcity.inc
$ wget http://geolite.maxmind.com/download/geoip/api/php/geoipregionvars.php
$ wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
$ wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCityv6-beta/GeoLiteCityv6.dat.gz
$ wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
$ wget http://geolite.maxmind.com/download/geoip/database/GeoIPv6.dat.gz
$ gunzip GeoLiteCity.dat.gz
$ gunzip GeoLiteCityv6.dat.gz
$ gunzip GeoIP.dat.gz
$ gunzip GeoIPv6.dat.gz

If you have php-gd already installed in server, you should edit 2 functions in geoip.inc to avoid conflict, cannot redeclare function error or something like that.

Replace the following geoip_country_code_by_name function

function geoip_country_code_by_name($gi, $name) {
  $country_id = geoip_country_id_by_name($gi,$name);
  if ($country_id !== false) {
        return $gi->GEOIP_COUNTRY_CODES[$country_id];
  }
  return false;
}

Into this

if (!function_exists('geoip_country_code_by_name')){
    function geoip_country_code_by_name($gi, $name) {
      $country_id = geoip_country_id_by_name($gi,$name);
      if ($country_id !== false) {
            return $gi->GEOIP_COUNTRY_CODES[$country_id];
      }
      return false;
    }
}

Replace the following geoip_country_name_by_name function

function geoip_country_name_by_name($gi, $name) {
  $country_id = geoip_country_id_by_name($gi,$name);
  if ($country_id !== false) {
        return $gi->GEOIP_COUNTRY_NAMES[$country_id];
  }
  return false;
}

Into this

if (!function_exists('geoip_country_name_by_name')){
    function geoip_country_name_by_name($gi, $name) {
      $country_id = geoip_country_id_by_name($gi,$name);
      if ($country_id !== false) {
            return $gi->GEOIP_COUNTRY_NAMES[$country_id];
      }
      return false;
    }
}

Create file called img.php

<?php
header("Cache-Control: no-cache, must-revalidate");
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");

$INC_DIR = $_SERVER["DOCUMENT_ROOT"] . "geoip-api/";

include($INC_DIR . "geoip.inc");
include($INC_DIR . "geoipcity.inc");
include($INC_DIR . "geoipregionvars.php");

$gi4 = geoip_open($INC_DIR . "GeoIP.dat",GEOIP_STANDARD);
$gi6 = geoip_open($INC_DIR . "GeoIPv6.dat",GEOIP_STANDARD);
$gicity4 = geoip_open($INC_DIR . "GeoLiteCity.dat",GEOIP_STANDARD);
$gicity6 = geoip_open($INC_DIR . "GeoLiteCityv6.dat",GEOIP_STANDARD);

$IpAddr = $_SERVER["REMOTE_ADDR"];

$osList = array
(
        'Windows 7' => 'windows nt 6.1',
        'Windows Vista' => 'windows nt 6.0',
        'Windows Server 2003' => 'windows nt 5.2',
        'Windows XP' => 'windows nt 5.1',
        'Windows 2000 sp1' => 'windows nt 5.01',
        'Windows 2000' => 'windows nt 5.0',
        'Windows NT 4.0' => 'windows nt 4.0',
        'Windows Me' => 'win 9x 4.9',
        'Windows 98' => 'windows 98',
        'Windows 95' => 'windows 95',
        'Windows CE' => 'windows ce',
        'Windows (version unknown)' => 'windows',
        'OpenBSD' => 'openbsd',
        'SunOS' => 'sunos',
        'Ubuntu' => 'ubuntu',
        'Linux' => '(linux)|(x11)',
        'Mac OSX Beta (Kodiak)' => 'mac os x beta',
        'Mac OSX Cheetah' => 'mac os x 10.0',
        'Mac OSX Puma' => 'mac os x 10.1',
        'Mac OSX Jaguar' => 'mac os x 10.2',
        'Mac OSX Panther' => 'mac os x 10.3',
        'Mac OSX Tiger' => 'mac os x 10.4',
        'Mac OSX Leopard' => 'mac os x 10.5',
        'Mac OSX Snow Leopard' => 'mac os x 10.6',
        'Mac OSX Lion' => 'mac os x 10.7',
        'Mac OSX (version unknown)' => 'mac os x',
        'Mac OS (classic)' => '(mac_powerpc)|(macintosh)',
        'QNX' => 'QNX',
        'BeOS' => 'beos',
        'OS2' => 'os/2',
        'SearchBot'=>'(nuhk)|(googlebot)|(yammybot)|(openbot)|(slurp)|(msnbot)|(ask jeeves/teoma)|(ia_archiver)'
);

$useragent = $_SERVER['HTTP_USER_AGENT'];
$useragent = strtolower($useragent);
$browser = getBrowser();

if(ipVersion($IpAddr) == 4) {
        $country = geoip_country_name_by_addr($gi4, $IpAddr);
        $cc = geoip_country_code_by_addr($gi4, $IpAddr);
        $record = geoip_record_by_addr($gicity4, $IpAddr);
        $region = $GEOIP_REGION_NAME[$record->country_code][$record->region];
        $flag = $_SERVER["DOCUMENT_ROOT"] . "flags/" . strtolower($cc) . ".png";
} else {
        $country = geoip_country_name_by_addr_v6($gi6, $IpAddr);
        $cc = geoip_country_code_by_addr_v6($gi6, $IpAddr);
        $record = geoip_record_by_addr_v6($gicity6, $IpAddr);
        $region = $GEOIP_REGION_NAME[$record->country_code][$record->region];
        $flag = $_SERVER["DOCUMENT_ROOT"] . "flags/"  . strtolower($cc) . ".png";
}

foreach($osList as $os=>$match) {
        if (preg_match('/' . $match . '/i', $useragent)) {
                break;
        } else {
                $os = "Undefined";
        }

}

create_badge($country, $cc, $region, $flag, $os, $browser['name'], $IpAddr, $proxy);

function create_badge($f_country, $f_cc, $f_region, $f_flag, $f_os, $f_browser, $f_ipaddr, $f_proxy)
{
        $width = 220;
        $height = 100;

        $badge_flag = imagecreatefrompng($f_flag);

        $image = ImageCreate($width, $height);

        $white = ImageColorAllocate($image, 255, 255, 255);
        $black = ImageColorAllocate($image, 0, 0, 0);
        $grey = ImageColorAllocate($image, 192, 192, 192);

        ImageFill($image, 0, 0, $grey);

        ImageString($image, 3, 5, 5,  "Country      : $f_country", $white);
        ImageString($image, 3, 5, 17, "Country Code : $f_cc", $white);
        ImageString($image, 3, 5, 29, "Region       : $f_region", $white);
        ImageString($image, 3, 5, 43, "Flag         : ", $white);
        Imagecopymerge($image, $badge_flag, 110, 45, 0, 0, 20, 11, 100);
        ImageString($image, 3, 5, 55, "OS           : $f_os", $white);
        ImageString($image, 3, 5, 67, "Browser      : $f_browser", $white);
        ImageString($image, 3, 5, 79, "IP           : $f_ipaddr", $white);

        header("Content-Type: image/jpeg");
        ImageJpeg($image);
        ImageDestroy($image);
}

function getBrowser()
{
        $u_agent = $_SERVER['HTTP_USER_AGENT'];
        $bname = 'Unknown';
        $platform = 'Unknown';

        if (preg_match('/linux/i', $u_agent)) {
                $platform = 'linux';
        }
        elseif (preg_match('/macintosh|mac os x/i', $u_agent)) {
                $platform = 'mac';
        }
        elseif (preg_match('/windows|win32/i', $u_agent)) {
                $platform = 'windows';
        }

        if(preg_match('/MSIE/i',$u_agent) && !preg_match('/Opera/i',$u_agent))
        {
                $bname = 'Internet Explorer';
                $ub = "MSIE";
        }
        elseif(preg_match('/Firefox/i',$u_agent))
        {
                $bname = 'Mozilla Firefox';
                $ub = "Firefox";
        }
        elseif(preg_match('/Chrome/i',$u_agent))
        {
                $bname = 'Google Chrome';
                $ub = "Chrome";
        }
        elseif(preg_match('/Safari/i',$u_agent))
        {
                $bname = 'Apple Safari';
                $ub = "Safari";
        }
        elseif(preg_match('/Opera/i',$u_agent))
        {
                $bname = 'Opera';
                $ub = "Opera";
        }
        elseif(preg_match('/Netscape/i',$u_agent))
        {
                $bname = 'Netscape';
                $ub = "Netscape";
        }

        return array(
                'userAgent' => $u_agent,
                'name'      => $bname,
                'version'   => $version,
                'platform'  => $platform,
        );
}

function ipVersion($ipvar) {
        return strpos($ipvar, ":") === false ? 4 : 6;
}

geoip_close($gi4);
geoip_close($gi6);
geoip_close($gicity4);
geoip_close($gicity6);

?>

Displaying the badge is simply by pointing browswer to http://www.example.com/geoip-api/img.php , or you can wrap in html img tag and add some nice style.

<img style="-moz-border-radius: 5px; -webkit-border-radius: 5px; -khtml-border-radius: 5px; border-radius: 5px;" src="img.php" alt="GeoIP" width="220" height="110" />

Or You can download here: geoip-gd-badge.tar.gz (36 downloads ) ,extract into your webserver.

cheers 🙂

2 Comments

  1. Bella

    I might be baentig a dead horse, but thank you for posting this!

Leave a Reply

Your email address will not be published. Required fields are marked *