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.)
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:
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.
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:
And an ANY query from “Spain”:
And what happens if we come from an unconfigured country?
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:
(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:
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