Valid XHTML 1.0!

ip2loc UDP Server



This software is maintained by Nerius "Rambetter" Landys. Don't hesitate to contact me at nlandys@gmail.com .



Section 1: Overview

This is open source software for running a simple UDP server that provides geolocations for IP addresses. A request packet incoming to the server will contain an IP address, and the response packet outgoing from the server will contain a country code, country name, region, city, latitude, and longitude for the IP address.

This software is distributed under a BSD-style license.

Here are some selling points of this software:

Drawbacks of using this software:

Please refer to the protocol for an explanation of the functionality that this server has to offer.



Section 2: Getting the Software

To get the source code and documentation for the ip2loc server:

rambetter@porky% svn checkout svn://svn.nerius.com/repos/ip2loc ./ip2loc

The most recent version of this HTML document that you are now reading is available as ./ip2loc/README.html .

Once you have the source code downloaded, the next step is to compile:

rambetter@porky% cd ./ip2loc/server/
rambetter@porky% make

This will generate two executables, ip2loc-server and ip2loc-csv-parser . You will need both of these programs.



Section 3: Compiling the IP2Location(TM) Database

To run the ip2loc server, you need to get your hands on some proprietary data that is provided by the company IP2Location(TM). The exact product that you need from them is DB5 (link here). If you prefer to use a freely available IP-to-location database instead, please see Section 4.

DB5 is downloaded as a ZIP archive named IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-FULL.ZIP . Inside this archive is a file named IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE.CSV . This CSV file is the only file that we need. Uncompressed, the CSV file is approximately 500 megabytes in size. Fortunately, it is a file consisting of plain text, and it compresses pretty well (the ZIP file distributable is about 70 megabytes in size).

The IP2Location(TM) proprietary IP-to-location data is updated once a month, at the beginning of each month. Usually, you buy a one-year subscription to DB5 for about $350, then download the updated data at the beginning of each month from IP2Location(TM).

We now need to "compile" the IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE.CSV file into a much more compact custom format that is needed to run the ip2loc server. To do this, we use the ip2loc-csv-parser command. This command is very simple: it takes no command-line arguments, reads the CSV file from standard input, and writes a compiled version of the database to standard output. Any parsing errors are displayed on standard error. Usually, you'll want to save the compiled IP-to-location database to a file named ip2loc.bin . To sum up, we run a command such as this:

rambetter@porky% cat IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE.CSV | ./ip2loc-csv-parser > ip2loc.bin

The generated ip2loc.bin file will be about 40 megabytes in size. You will need this file to run the ip2loc server, so keep it handy. The ip2loc.bin file compresses to about 20 megabytes using standard compression tools such as gzip . You may consider storing it compressed if disk usage is an issue, in which case you'll want to gunzip the file on-the-fly when you need to use it later on.

For reference, below is a sample of actual contents of the file IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE.CSV .

"0","16777215","-","-","-","-","0.000000","0.000000"
"16777216","16777471","AU","AUSTRALIA","QUEENSLAND","SOUTH BRISBANE","-27.483330","153.016670"
"16777472","16778239","CN","CHINA","FUJIAN","FUZHOU","26.061390","119.306110"
"16778240","16778495","AU","AUSTRALIA","VICTORIA","MELBOURNE","-37.814000","144.963320"
"16778496","16778751","AU","AUSTRALIA","NEW SOUTH WALES","SYDNEY","-33.867850","151.207320"
"16778752","16779263","AU","AUSTRALIA","-","-","-33.867850","151.207320"
"16779264","16781311","CN","CHINA","GUANGDONG","GUANGZHOU","23.116670","113.250000"
.....


Section 4: Compiling the MaxMind Database

The ip2loc server also supports IP-to-location data freely available from MaxMind. This section explains how to use this alternate database with the ip2loc server.

We need to compile an extra program which translates the MaxMind data into something ip2loc understands.

rambetter@porky% make all

This generates an additional executable called ip2loc-csv-parser-maxmind . You can run this program with no arguments to get a better idea of how to use it:

rambetter@porky% ./ip2loc-csv-parser-maxmind
Usage:
  ./ip2loc-csv-parser-maxmind <country-csv> <IP-blocks-csv> <locations-csv>
The first argument would typically be a file named "GeoIPCountryWhois.csv".
The second argument would typically be a file named "GeoLiteCity-Blocks.csv".
The third argument would typically be a file named "GeoLiteCity-Location.csv".
A database file is written to standard out.

The files you need can be downloaded from the MaxMind GeoLite Free Downloadable Databases page. In particular, you need the GeoLite Country CSV and the GeoLite City CSV. Inside these two downloads you should find the three files mentioned above.

For reference, I provide a sample of these files' contents. The GeoIPCountryWhois.csv file looks like so:

"1.0.0.0","1.0.0.255","16777216","16777471","AU","Australia"
"1.0.1.0","1.0.3.255","16777472","16778239","CN","China"
"1.0.4.0","1.0.7.255","16778240","16779263","AU","Australia"
"1.0.8.0","1.0.15.255","16779264","16781311","CN","China"
"1.0.16.0","1.0.31.255","16781312","16785407","JP","Japan"
.....

The GeoLiteCity-Blocks.csv file looks like so:

Copyright (c) 2011 MaxMind Inc.  All Rights Reserved.
startIpNum,endIpNum,locId
"16777216","16777471","17"
"16777472","16777727","104084"
"16777728","16778239","49"
"16778240","16778751","14409"
.....

The GeoLiteCity-Location.csv file looks like so:

Copyright (c) 2012 MaxMind LLC.  All Rights Reserved.
locId,country,region,city,postalCode,latitude,longitude,metroCode,areaCode
1,"O1","","","",0.0000,0.0000,,
2,"AP","","","",35.0000,105.0000,,
.....
10925,"US","CA","Solana Beach","92075",32.9959,-117.2571,825,858
10926,"US","NY","Rochester","14621",43.1858,-77.6024,538,585
.....

Finally, we run the parser program to generate the compiled database:

rambetter@porky% ./ip2loc-csv-parser-maxmind \
  /path/to/GeoIPCountryWhois.csv \
  /path/to/GeoLiteCity-Blocks.csv \
  /path/to/GeoLiteCity-Location.csv > ip2loc.bin

You will need the generated ip2loc.bin file to run the ip2loc server, so keep it handy.



Section 5: Launching the Server Process

The executable to launch the server process is ip2loc-server . You can run this binary with no arguments to get a better idea of how to use it:

rambetter@porky% ./ip2loc-server
Usage:
  ./ip2loc-server <listen-port> <listen-IP>
The second argument, the IP address to listen on, can be omitted, in which
case the server will listen on all available interfaces.  This program reads
standard input, which should be the contents of an "ip2loc.bin" file.  A
file in the current directory named ".password" will be read, which should
consist of a single line of text specifying the password that the server will
be protected by.

The server actually only listens on IPv4 addresses. Listening on IPv6 interfaces is not yet supported. So, to run the server, we'll do something like this:

rambetter@porky% echo "pa55w0rd" > .password
rambetter@porky% cat ip2loc.bin | ./ip2loc-server 10020 127.0.0.1

Note that the server process runs in the foreground. If you want to launch the server in your shell and then leave the server running after logging out of your shell, you'll need to either use a tool such as screen or use a utility such as /usr/sbin/daemon on FreeBSD to launch the server process. Also, you probably want to pipe standard error to a log file. Nothing on standard out will be generated by the server program.



Section 6: Protocol Reference

This section describes the communication over network UDP that happens with the ip2loc server.

The country code, country, region, and city strings will always consist of ASCII text (punctuation characters such as commas may be present). These strings will never contain the double quote character '"' or the newline character '\n'. Also, no lowercase letters ('a' through 'z') will be present in the strings.


getLocationForIP

A request to the ip2loc server looks like this (over UDP):

ip2locRequest
pa55w0rd
getLocationForIP:0e7baf16
64.156.193.115

The lines of text in the request are separated by a single newline character. There must be a trailing newline after the packet. The second line must match the contents of the .password file exactly, and the last line is the IP address to be queried.

In the example above, the third line contains what is called a challenge field. This is the colon followed by the string "0e7baf16". Including a challenge in the request is optional; that is, the colon and everything coming after it can be omitted from the request. If the challenge is included, it must be a string consisting of exactly 8 characters, and each character must be a digit or a lowercase letter between 'a' and 'f'. In essence, the challenge is a 32 bit field represented in hexadecimal format. If the challenge is included in the request, it will be echoed back in the response. The purpose of the challenge is to ensure that a response really came from the server and was not just a spoofed packet coming from some random location on the internet.

A response looks like this:

ip2locResponse
getLocationForIP:0e7baf16
64.156.193.115

US
UNITED STATES
CALIFORNIA
SAN DIEGO
32.7473
-117.148

Here, "US" is the country code, "UNITED STATES" is the country, "CALIFORNIA" is the region, and "SAN DIEGO" is the city. The second to last line is the latitude and the last line is the longitude. It may happen that some of the values for country code, country, region, and city will not be present. If that is the case, they will appear as blank lines. The lines of the response packet are separated by a single newline character. The last line is terminated by a newline character.

Because our sample request contained a challenge, the response has a challenge also. This is the string "0e7baf16" after the colon on the second line of the response. The challenge in the response will equal to the challenge in the request. If no challenge was present in the request, no challenge field will appear in the response (the ':' character plus everything after it will be missing).

If the password is incorrect or if the request packet is malformed in some other way, the server will silently drop the packet without sending a response of any kind.


getLocationForIP (Quake III version)

A request to the ip2loc server looks like this (over UDP):

@@@@ip2locRequest
pa55w0rd
getLocationForIP:499a02ec
64.156.193.115

@@@@ is the 4 byte quantity consisting of all 1 bits (0xffffffff).

If the request is well-formed and if the password is correct, the server will respond with a UDP packet that looks like this:

@@@@ip2LocResponse "getLocationForIP:499a02ec" "64.156.193.115" "US" "UNITED STATES" "CALIFORNIA" "SAN DIEGO" "32.7473" "-117.148"

The response does not have a trailing newline. The challenge field is optional (see example above).


quit

You can shut down the ip2loc server process by sending a "quit" request. However, this request must be sent from the loopback (127.0.0.1) address. If the ip2loc server is not listening on the loopback interface, the packet must be sent from the same IP address that the server process is listening on.

ip2locRequest
pa55w0rd
quit

No response is returned for this type of packet. A challenge field may be present after the "quit" command, but it serves no purpose.



Section 7: PHP Client

A PHP client library is available for communicating with the ip2loc server. After you check out the ip2loc source code from SVN as described in Section 2, the PHP client library will be located in file ./ip2loc/client/php/ip2loc.php . There are some PHP test scripts that demonstrate usage of the PHP client library in the same directory as ip2loc.php . A bare minimum PHP example without any error checking would look like this:

<?php

require_once "ip2loc.php";

$ip = "99.50.206.241"; 
$ip2loc = new ip2loc("pa55w0rd", "localhost", 10020);
$locations = $ip2loc->get_locations(array($ip));
$ip2loc->close();
echo $ip . " is from " . $locations[$ip]->country() . "\n";

?>

In practice, however, you would want to write slightly more code to perform some error checking. Let's take an example. Suppose you are running forums software such as phpBB or Simple Machines Forum (SMF). Suppose you've been getting lots of spam from some countries. You would like to block any activity from these countries to any part of your website. First, create a file named ip2loc-connect-info.php and put that into one of your PHP includes directories. (I like to keep this sensitive information in a location away from the webroot for security reasons.) The contents of ip2loc-connect-info.php will be the following:

<?php

define("IP2LOC_SERV_PASSWORD", "pa55w0rd");
define("IP2LOC_SERV_ADDRESS", "localhost");
define("IP2LOC_SERV_PORT", 10020);
define("IP2LOC_TIMEOUT", 200);
define("IP2LOC_RETRIES", 1);

?>

Then, you should find a file that is included at the start of every webpage that a user can hit on your website. In the case of phpBB, such a file is common.php in webroot. In the case of SMF 1.1.x, such a file is index.php in webroot. At the very top of such a file, you can add PHP code such as the following. Bear in mind that this code should be executed before any data is written to the client because the function header() writes an HTTP header to the client, and a header can't be changed once data in the body of the HTTP response is written.

require_once "ip2loc.php";
require_once "ip2loc-connect-info.php";

$client_ip = $_SERVER["REMOTE_ADDR"];
while (TRUE) { // Provide break structure.
  $ip2loc = NULL;
  try { $ip2loc = new ip2loc(IP2LOC_SERV_PASSWORD, IP2LOC_SERV_ADDRESS,
                             IP2LOC_SERV_PORT, IP2LOC_TIMEOUT, IP2LOC_RETRIES); }
  catch (Exception $e) { break; } // Don't prevent website access due to misconfigured ip2loc.
  $locations = NULL;
  try { $locations = $ip2loc->get_locations(array($client_ip)); }
  catch (Exception $e) {}  // $locations will remain NULL.
  $ip2loc->close();
  if ($locations === NULL) { break; }  // Don't prevent website access due to misconfigured ip2loc.
  $location = $locations[$client_ip];
  if ($location !== NULL) {
    $banned_countries = array("ZZ", // fictitious country code
                              "WW"); // fictitious country code
    for ($inx = count($banned_countries) - 1; $inx >= 0; $inx--) {
      if (strcmp($location->country_code(), $banned_countries[$inx]) === 0) {
        header("Location: /banned.html");
        exit;
      }
    }
  }
  break;
}

At the end of this script, the PHP page redirects the client to banned.html in webroot. You should create such a page with a message stating why they are banned. Frankly though, I don't know that banning an entire country from a website is a good idea.