When an authoritative DNS name server is queried it knows the address of the recursive caching server which queried it, and based on this information, it can return a different response depending on the source address. This is typically knows as GeoDNS or GeoIP-based DNS, and it is often used to return the address of a resource which is closest (network-wise) to the user’s resolver.
Aki Tuomi’s GeoIP back-end for PowerDNS does just that. The location information on a by-country basis can be obtained from MaxMind’s GeoLite database, or you can create your own.
We launch PowerDNS and configure the back-end with a few directives, basically giving it the path to the location data and to a YAML configuration file which defines the geo-enabled zones we provide on the server. (YAML is the same markup language we use in Ansible.)
launch=geoip
geoip-database-file=/usr/share/GeoIP/GeoIP.dat
geoip-database-file6=/usr/share/GeoIP/GeoIPv6.dat
geoip-zones-file=/home/jpm/src/powerdns/geo.yml
geoip-dnssec-keydir=/home/jpm/src/powerdns/geo.keys
There is an additional configuration directive called geoip-database-cache
with which I specify what kind of caching should be done on the database.
standard
, the default if unspecified, has the back-end read the database from the file system and consumes little memory.memory
loads the whole database into RAM which provides for high performanceindex
caches frequently accessed index portions of the database only, which is faster thanstandard
and consumes less RAM thanmemory
mmap
loads the database into memory-mapped RAM
Since I’m not in a position to test this at the moment from real addresses, or rather since I’m not willing to show you real addresses, I cobbled my own GeoIP.dat. Instead of using mmutils which ought to work, I compiled geoip-csv-to-dat and fed it this CSV based on a real one:
"192.168.1.196","192.168.1.196","34910976","34911231","ES","Spain"
"192.168.1.10","192.168.1.10","34621952","34622463","NL","Netherlands"
"127.0.0.1","127.0.0.255","34880512","34881535","DE","Germany"
"172.0.0.0","172.255.255.255","34938880","34947071","FR","France"
After getting that out of the way, we can create our geoip-zones-file
. I add a single zone called geo.example.org
with four records; one for each of the countries we support directly, and a wildcard country (note the quotes on that line – they’re an artifact of YAML syntax). Each of the records can have as many DNS resource records as I want, as long as they contain valid DNS rdata obviously.
domains:
- domain: geo.example.org
ttl: 60
records:
geo.example.org:
- soa: ns.example.org. geoman.example.org. 1 7200 3600 86400 60
- ns: ns.example.org.
deu.geo.example.org:
- a: 192.0.0.2
- txt: Guten Tag
esp.geo.example.org:
- a: 192.0.0.10
- txt: Muy buenos dias
- loc: 40 8 43.041 N 3 21 42.539 W 714m 10m 100m 10m
"*.geo.example.org":
- a: 127.0.0.53
- txt: I don't know exactly where you are
services:
www.geo.example.org: '%co.geo.example.org'
This configuration provides the server with answers for questions along the lines of what is the A record for esp.geo.example.org
?, and what is its LOC record?. Additionally we create a service called www.geo.example.org
which defines a service which people will query for. This will direct the server to return answers for the actual query of country.geo.example.org. The %co
is expanded by the geoip back-end, as follows:
%co
is the 3-letter ISO country code%cn
is the continent%af
is replaced by the address family, i.e."v4"
or"v6"
depending on whether the query originated from an IPv4 or IPv6 address respectively%hh
,%dd
,%mo
,%wd
are replaced by two digits of hour, day of the month, month, and weekday (UTC) respectively, whereas%mos
and%wds
are short strings which correspond to the month (jan
,feb
) and weekday (mon
,tue
) respectively. We can use this to direct clients to specific servers during, say, periodic maintenance times.
So, let’s try an IPv4 address query from localhost:
;; ANSWER SECTION:
www.geo.example.org. 60 IN A 192.0.0.2
And an ANY query from “Spain”:
;; ANSWER SECTION:
www.geo.example.org. 60 IN LOC 40 8 43.041 N 3 21 42.539 W 714.00m 10m 100m 10m
www.geo.example.org. 60 IN A 192.0.0.10
www.geo.example.org. 60 IN TXT "Muy buenos dias"
And what happens if we come from an unconfigured country?
;; ANSWER SECTION:
www.geo.example.org. 60 IN CNAME unknown.geo.example.org.
unknown.geo.example.org. 60 IN A 127.0.0.53
unknown.geo.example.org. 60 IN TXT "I don't know exactly where you are"
DNSSEC
You may have noticed the geoip-dnssec-keydir
parameter in our configuration above; adding this will enable DNSSEC on this back-end, assuming the specified directory exists, is writeable by pdnssec, and readable by the server. This keydir stores keys in BIND’s Private-key-format: v1.2 (which IIRC hasn’t really been formally documented), and the filenames are built from zone name, key flags and active/disabled state encoded into them. To actually get our zone to produce DNSSEC data, we create at least one key for the zone:
$ pdnssec secure-zone geo.example.org
Securing zone with rsasha256 algorithm with default key size
Zone geo.example.org. secured
Erasing NSEC3 ordering since we are narrow, only setting 'auth' fields
$ ls -l geo.keys/
-rw-rw-r--. 1 jpm jpm 939 Nov 12 11:56 geo.example.org.256.2.1.key
-rw-rw-r--. 1 jpm jpm 1703 Nov 12 11:56 geo.example.org.257.1.1.key
(It’s probably worth pointing out at this time that the pdnssec utility will be renamed to pdnsutil very soon.)
I don’t have to restart PowerDNS to obtain DNSSEC-signed responses:
;; flags: qr aa; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1680
;; QUESTION SECTION:
;www.geo.example.org. IN ANY
;; ANSWER SECTION:
www.geo.example.org. 60 IN RRSIG TXT 8 4 60 (
20151126000000 20151105000000 32029 geo.example.org.
lAtl9z/vVSOGpTX0XUHFyP1wgSofdslUB9KeLe5FlsBS
RaUBgNNgOOLPU56c3JuQWWT8zv9hlBNySMjJSFQ8OdbQ
CQj5gfLVgYc5GItztO72c8kJzpdEgodEhYKgF88QX7sB
oNwyIS6djdgX5NyfXSfa6Dd8fAVkjfIVzpgDsMc= )
www.geo.example.org. 60 IN A 192.0.0.2
www.geo.example.org. 60 IN TXT "Guten Tag"
www.geo.example.org. 60 IN RRSIG A 8 4 60 (
20151126000000 20151105000000 32029 geo.example.org.
S/jAfa5sIfDJRF+/GGOB4ZJDTc9vLHAfw6fio7mwkS00
RDpOqHJ/JizChHHwSU/xAdEme4+BmUMhcTxHP4M8NSY8
X7GSVcdvtXVG9rWl8ebnGNhaNRAKAmGnWVn2+s4Eemoa
BCIi0+GUtEt0QNioZkBlvL33N1Wf1HaZDSc2LrQ= )
I’ve said it before, and I’ll say it again, and you can’t stop me saying it: enabling DNSSEC doesn’t get easier than with PowerDNS.
Back on topic, the geoip back-end is easy to configure, and it is powerful. Our friends at PowerDNS are currently discussing adding a feature will will allow me to specify subnets directly in the YAML file.
If you want to see this live, fire off a TXT
query to www.geo.powerdns.com
.