Feb 162015
MaxMind offer a fantastic resource to geolocate IP addresses. Unfortunately the PHP API supplied is overkill for most (simple) uses. Instead, we can do it mostly within PHP/MySQL.
Create Schema:
DROP TABLE IF EXISTS `blocks`; CREATE TABLE `blocks` ( `startIPNum` int(10) unsigned NOT NULL, `endIPNum` int(10) unsigned NOT NULL, `locID` int(10) unsigned NOT NULL, PRIMARY KEY (`startIPNum`,`endIPNum`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 PACK_KEYS=1 DELAY_KEY_WRITE=1;
DROP TABLE IF EXISTS `location`; CREATE TABLE `location` ( `locID` int(10) unsigned NOT NULL, `country` char(2) default NULL, `region` char(2) default NULL, `city` varchar(45) default NULL, `postalCode` char(7) default NULL, `latitude` double default NULL, `longitude` double default NULL, `dmaCode` char(3) default NULL, `areaCode` char(3) default NULL, PRIMARY KEY (`locID`), KEY `Index_Country` (`country`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 ROW_FORMAT=FIXED;
Read in the MaxMind data:
load data infile 'GeoLiteCity-Blocks.csv' into table `blocks` fields terminated by ',' optionally enclosed by '"' lines terminated by '\n' ignore 2 lines;
load data infile 'GeoLiteCity-Location.csv' into table `location` fields terminated by ',' optionally enclosed by '"' lines terminated by '\n' ignore 2 lines;
And then we can use raw SQL to query against it – extremely quickly (0.0001 sec) by using the following query:
SELECT * FROM `location` WHERE locid = ( SELECT locid FROM `blocks` INNER JOIN ( SELECT Max(startipnum) AS start FROM `blocks` WHERE startipnum <= inet_aton('1.2.3.4')) AS s ON ( startipnum = s.start ) WHERE endipnum >= inet_aton('1.2.3.4'));
You can similarly use this almost exact query within PHP using the following function (assuming PDO….):
function IPdata($ip) { global $dbh; if(!isset($dbh)) die("No database connection available."); if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { $cip = ip2long($ip); if($cip) { try { $stmt = $dbh->prepare("SELECT * FROM `ip_geolocation` WHERE locID = (SELECT locID FROM `ip_blocks` INNER JOIN (SELECT MAX(startIPNum) AS start FROM `ip_blocks` WHERE startIPNum <= :IPADDR1) AS s ON (startIPNum = s.start) WHERE endIPNum >= :IPADDR2)"); $stmt->bindParam(':IPADDR1', $cip); $stmt->bindParam(':IPADDR2', $cip); $stmt->execute(); } catch (PDOException $ex) { die("IPdata() SQL ERROR: " . $ex->getMessage()); return false; } if($stmt->rowCount() == 1) { $row = $stmt->fetch(); return $row; } else { die("IPdata() ERROR: NO DATA RETURNED FOR {$ip}"); return false; } } else { die("IPdata() ERROR: ip2long RETURNED FALSE FOR {$ip}:{$cip}"); return false; } } else { die("IPdata() ERROR: IP address does not validate ({$ip}:{$cip})"); return false; } }
Ham?