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.
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.
memoryloads the whole database into RAM which provides for high performance
indexcaches frequently accessed index portions of the database only, which is faster than
standardand consumes less RAM than
mmaploads 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" "188.8.131.52","184.108.40.206","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: 220.127.116.11 - 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:
%cois the 3-letter ISO country code
%cnis the continent
%afis replaced by the address family, i.e.
"v6"depending on whether the query originated from an IPv4 or IPv6 address respectively
%wdare replaced by two digits of hour, day of the month, month, and weekday (UTC) respectively, whereas
%wdsare short strings which correspond to the month (
feb) and weekday (
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 18.104.22.168 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"
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