Documentation

The data file usually found at /etc/geoipdns-1.2.3.4/root/data contains all of the geographic information (IP to location map) and the names to be served. This file must be compiled into CDB format before geoipdns can use it. (running "make" in this directory will accomplish this) Updates can be made to this file "on the fly" without restarting geoipdns.

Data files usually consist of two significant sections, the LOC (location) record section and the name record section. A LOC record might look like this:

%boston:71.232.0.0:16

where "boston" is the name of the location and 71.232.0.0:16 refers to the 71.232.0.0/16 network. Once we build out enough locations for the IP space we want to cover, we create name entries. For example:

+www.example.com:11.11.11.11:1200::boston
+www.example.com:99.99.99.99:1200::nomatch

Requesters with IPs based in 71.232.0.0/16 (the location called "boston") will get 11.11.11.11 for www.example.com while all others will get 99.99.99.99. You can scope your LOC and name records to whatever level of detail you need.

With this general overview in mind, lets look at a more practical example. The first step is to generate LOC records that cover the public IP space. How you do this depends heavily upon your needs but for this example we will generate a worldwide map of IP addresses down to the city level. A good source for this IP address geographic data is MaxMind who release GeoLite City on a monthly basis. The data is freely available and generally more accurate than we need. Download the latest GeoLite City database in CSV format and unzip the file.

The resulting files, GeoLiteCity-Blocks.csv and GeoLiteCity-Location.csv, are not in an ideal format for us so geoipdns-geolitecity-build.rb can be used to combine them into something a little more convenient for us. Put geoipdns-geolitecity-build.rb somewhere your shell can find it such as /usr/local/bin and run it against the GeoLiteCity database:

wget http://geoipdns.org/download/geoipdns-geolitecity-build.rb
chmod 755 geoipdns-geolitecity-build.rb
mv geoipdns-geolitecity-build.rb /usr/local/bin
cd GeoLiteCity_*
geoipdns-geolitecity-build.rb

Note: If geoipdns-geolitecity-build.rb complains about a missing interpreter, adjust the first line in the file to point to your ruby executable.

geoipdns-geolitecity-build.rb will parse GeoLiteCity-Blocks.csv and GeoLiteCity-Location.csv in the local directory creating geoipdata.csv which is a more convenient format. This file will have roughly 5 million rows and look something like this:

network,country,region,city,postalCode,latitude,longitude,metroCode,areaCode
"4.137.223.0/24", "US", "GA", "Conyers", "30094", 33.6093, -84.0614, 524, 770
"4.137.224.0/21", "US", "NC", "Charlotte", "", 35.2060, -80.8290, 517, 704
"4.137.232.0/21", "US", "NC", "Charlotte", "28212", 35.1862, -80.7479, 517, 704
"4.137.240.0/22", "US", "GA", "Columbus", "", 32.4845, -84.9494, 522, 706
"4.137.244.0/22", "US", "TN", "Powell", "37849", 36.0684, -84.0197, 557, 865
"4.137.248.0/22", "US", "AL", "Huntsville", "", 34.7018, -86.6108, 691, 256
"4.137.252.0/22", "US", "TN", "Powell", "37849", 36.0684, -84.0197, 557, 865
"4.138.23.0/24", "US", "MT", "Choteau", "59422", 47.8665, -112.4488, 755, 406
"4.138.24.0/23", "US", "NC", "Kenly", "27542", 35.6099, -78.1506, 560, 919
"4.138.26.0/24", "US", "NC", "Wake Forest", "27587", 35.9767, -78.5544, 560, 919
"4.138.27.0/24", "US", "NC", "Wake Forest", "", 35.9767, -78.5544, 560, 919
"4.138.28.0/22", "US", "NC", "Wake Forest", "", 35.9767, -78.5544, 560, 919
"4.138.206.0/24", "US", "MA", "Chicopee", "01020", 42.1861, -72.5255, 543, 413
"4.138.207.0/24", "US", "MA", "Chicopee", "", 42.1852, -72.5636, 543, 413
"4.138.208.0/21", "US", "MA", "Medway", "02053", 42.1556, -71.4268, 506, 508
"4.138.216.0/22", "US", "NY", "Middletown", "10940", 41.4487, -74.4690, 501, 845
"4.138.220.0/22", "US", "NY", "Odessa", "14869", 42.3680, -76.7728, 565, 607
"4.138.224.0/22", "US", "MA", "Fall River", "", 41.6950, -71.1443, 521, 508
"4.138.230.0/23", "US", "MA", "Rockport", "01966", 42.6593, -70.5991, 506, 978
"4.138.232.0/22", "US", "MA", "Kingston", "02364", 41.9783, -70.7467, 506, 781
"91.85.227.0/29", "GB", "D4", "Exeter", "", 50.7000, -3.5333, , 
"91.85.227.8/29", "GB", "F2", "Andover", "", 51.2000, -1.4833, , 
"91.85.227.16/28", "GB", "D4", "Exeter", "", 50.7000, -3.5333, , 
"91.85.227.32/28", "GB", "D4", "Exeter", "", 50.7000, -3.5333, , 
"91.85.227.48/29", "GB", "D4", "Exeter", "", 50.7000, -3.5333, , 
"91.85.227.56/29", "GB", "", "", "", 54.0000, -2.0000, , 
"91.85.227.64/26", "GB", "D4", "Exeter", "", 50.7000, -3.5333, , 
"91.85.227.128/25", "GB", "D4", "Exeter", "", 50.7000, -3.5333, , 
"91.85.228.0/29", "GB", "", "", "", 54.0000, -2.0000, , 
"91.85.228.8/29", "GB", "D4", "Exeter", "", 50.7000, -3.5333, , 
"91.85.228.16/29", "GB", "", "", "", 54.0000, -2.0000, , 
"91.85.228.24/29", "GB", "D4", "Exeter", "", 50.7000, -3.5333, , 
"91.85.228.32/27", "GB", "D4", "Exeter", "", 50.7000, -3.5333, , 
"91.85.228.64/27", "GB", "D4", "Exeter", "", 50.7000, -3.5333, , 
"91.85.228.96/28", "GB", "D4", "Exeter", "", 50.7000, -3.5333, , 

The next step is to build the LOC portion of our data file. As this is somewhat more implementation specific, we will give a common example with the understanding that you will adjust things as necessary to suit your needs. Lets build LOC records that cut the United States and Canada roughly into East coast, West coast and Central. This might be useful for a CDN or SIP service that mantains servers in New York, Chicago and Los Angeles by sending users to their most local server. To keep things simple, we won't be very exact in this example although we do have the latitude and longitude in the data so feel free to experiment.

Running geoipdns-build-location-data.rb will read in the data-blocks file we created above and output only the records within the United States or Canada marking them us-east, us-central, us-west, canada-east, canada-central and canada-west apropriately. You'll need to run this script within the directory contaning geoipdata.csv.

wget http://geoipdns.org/download/geoipdns-build-location-data.rb
chmod 755 geoipdns-build-location-data.rb

The resulting file ("data-blocks") only contains rows for IPs within the United States and Canada. This cuts our location data file down from roughly 5 million rows to just over 2.2 million. Our next step will be to build up a "data" file and append "data-blocks" file to the end. Let's add a few lines to a new file called "data-in" to hold our actual names. We will make the name "www.example.com" resolve differently depending on the requestor's location. Our "data-in" file might look like this:

.example.com:1.2.3.4:a

+www.example.com:11.11.11.11:1200::us-east
+www.example.com:22.22.22.22:1200::us-central
+www.example.com:33.33.33.33:1200::us-west
+www.example.com:44.44.44.44:1200::canada-east
+www.example.com:55.55.55.55:1200::canada-central
+www.example.com:66.66.66.66:1200::canada-west
+www.example.com:99.99.99.99:1200::nomatch

Simply put, the first line designates 1.2.3.4 as a nameserver authoritative over example.com. The next several lines create different records for each of the regions we defined in our "data-blocks" file. Finally, the last line is a nomatch target which matches if no other line does. This way if we only wanted to do something different for us-west, we could remove everything else except the nomatch target and everything would work. If you remove the nomatch target, geoipdns won't respond to requests outside the defined regions which would effectively create a DNS blackhole for those users.

Our next step is to concatinate "data-in" and "data-blocks" into one file called "data" usually available at /etc/geoipdns-1.2.3.4/root/data where /etc/geoipdns-1.2.3.4 is the directory where you installed GeoIPDNS.

cat data-in data-blocks > /etc/geoipdns-1.2.3.4/root/data

After editing this file, type make in that directory to build your data.cdb file. The geoipdns server uses the compiled version of this file ("data.cdb") to serve names. Don't forget to re-run make when you make changes to your "data" file. There is no need to restart the server at each build as cdb creation is an atomic operation and geoipdns watches this file for updates.

At this point we should have a working location sensitive DNS server. Give it a try by looking up the name from different locations.

NY-server ~ $ hostx www.example.com 1.2.3.4
www.example.com        A        1.1.1.1
CA-server ~ $ hostx www.example.com 1.2.3.4
www.example.com        A        3.3.3.3

A handy online tool for seeing how your names are resolved from different locations around the world is http://www.whatsmydns.net/ which lets you enter real names and see how they resolve in various locations around the world. Try geotest.rnd.bandwidth.com as an example.

If things don't seem to be working, it can be easier to test things without the weight of over 2 million rows in your "data" file. Try creating a simple data file first and move forward as you confirm things working. Here is a complete data file for testing.

.example.com:1.2.3.4:a

%boston:71.233.148.0:24
%boston:71.232.0.0:16
%new-york:71.250.0.0:16
%new-york:71.251.10.0:24

+www.example.com:11.11.11.11:1200::boston
+www.example.com:22.22.22.22:1200::new-york
+www.example.com:99.99.99.99:1200::nomatch

Clients requesting 'www.example.com' from within one of the 'boston' blocks (71.233.148.0/24 or 71.232.0.0/16) will get the answer '11.11.11.11' while clients requesting the same name from any of the blocks labeled 'new-york' (71.250.0.0/16 or 71.251.10.0/24) will get '22.22.22.22'. Clients from outside any of the listed blocks are returned the nomatch target '99.99.99.99'.

The format for LOC (location) records is:

%target:ipnet:bitnetmask:mapname:username
although mapname and username are optional. All the traditional record types now accept a target (making the record LOC-enabled) and optional mapname and username parameters. For example:
=fqdn:ip:ttl:timestamp:target:mapname:username
+fqdn:ip:ttl:timestamp:target:mapname:username
@fqdn:ip:x:dist:ttl:timestamp:target:mapname:username
Cfqdn:p:ttl:timestamp:target:mapname:username
'fqdn:s:ttl:timestamp:target:mapname:username
^fqdn:p:ttl:timestamp:target:mapname:username
The standard tinydns format will still work but only names with targets defined will cause a location sensitive lookup. Therefore you can use an already existing tinydns data file and add location-aware names only where needed. geoipdns skips looking up the requestor's block if there isn't a LOC target defined amongst the answers.