The latest release of our authoritative DNS server, Knot DNS 2.7, comes with several new features. One of them is the GeoIP module for geography-based or subnet-based responses. In this article, we will briefly explain what the module is for and how it works and then we will explore how to set up and configure the module on your Knot server.
A simple example
If you have never encountered GeoIP in DNS, consider the following illustrative example. Imagine that you want to serve your website, www.example.com, to many clients all over the world. Then you might store the data of your site on a single server, accessed by all of your clients, or you might have multiple copies of the site stored on several data servers around the world. Now in the second case, when a client requests the IP address of the server, there are many possible answers. Wouldn’t it be great to choose the server which is the closest to the client and therefore save time on routing and minimize latency? GeoIP in DNS servers such as BIND, PowerDNS and now also Knot DNS is a mechanism that does just that.
The figure below illustrates the case where the DNS server makes decisions according to the country from which the request for your service is coming. If the address of the client is determined to be located in Norway, for example, the DNS server responds with the address of your Norwegian mirror server. However, there is no mirror server for your hypothetical web service in Italy yet, so the DNS server responds to the Italian client with the address of the default data server.
What our module can do
The GeoIP module in Knot can operate in two different modes: subnet and geodb. In the subnet mode, the appropriate response is chosen based on whether the client’s source address belongs to a particular subnet of IPv4 or IPv6 addresses. This mode is simple to use as it does not require any external databases.
In the geodb mode, the response is chosen based on geographical data associated with the client’s address, which are retrieved from an external GeoIP database in the MaxMind format, such as GeoIP2 or the free GeoLite2. Depending on the database and the configuration of the module, the choice of the response may be tailored according to the client’s continent, country, city, autonomous system number, ISP and more.
Finally, our GeoIP module also has DNSSEC support, which can be enabled in multiple ways. See the documentation for details.
How to use it
To walk through the tutorial you need to have Knot DNS 2.7.0 or newer installed (see here for download), and a DNS lookup utility, either our kdig or ISC BIND’s dig. To test the geodb mode of the module, download and unzip the free GeoLite2 City database. It is of course very useful to have experience in configuring and operating Knot DNS. Finally, create a tiny example.com.zone zonefile for our examples, with the following content:
$ORIGIN example.com. $TTL 3600 @ SOA dns1.example.com. hostmaster.example.com. ( 2010111213 ; serial 6h ; refresh 1h ; retry 1w ; expire 1d ) ; minimum NS dns1 dns1 A 192.0.2.1 www A 203.0.113.50 TXT "default server"
We will use a total of three short configuration files in our setup. The main server configuration file (e.g. /etc/knot/knot.conf), one file for the module in the subnet mode (let’s name it net.conf) and one for the geodb mode, which we will name geo.conf.
To try out both of the described modes of the module, we will add two mod-geoip sections to the main server configuration file. We will also enable the EDNS Client Subnet option in the server section, which we will later use to test if everything works correctly. This is how the entire miniature configuration file may look like:
server: listen: 127.0.0.1@53 edns-client-subnet: on mod-geoip: - id: net config-file: "/path/to/net.conf" mode: subnet mod-geoip: - id: geo config-file: "/path/to/geo.conf" mode: geodb geodb-file: "/path/to/GeoLite2-City.mmdb" geodb-key: [country/iso_code, city/names/en] zone: - domain: example.com. file: "/path/to/example.com.zone" module: mod-geoip/net module: mod-geoip/geo
This configuration creates two instances of the GeoIP module with separate id’s and configuration files. The geodb-file option specifies the location of the MaxMind database to be used, here we use the GeoLite2 City database which we downloaded at the start. The geodb-key option specifies a list of database keys according to which the responses will be tailored. Here we use the client’s country referred to by its ISO code, and if available, their city specified by its name in English (note that the order in which the keys are given is important).
The next step is to create the module config files. A simple net.conf may look as follows:
www.example.com: - net: 192.0.2.0/24 A: 192.0.2.50 - net: 198.51.100.0/24 A: 198.51.100.50
This syntax means that if a client queries for the A record of the domain www.example.com from an address in the range 192.0.2.0-192.0.2.255, Knot will respond with the 192.0.2.50 and similarly with the second option. If a client sends a query from a different address, they will receive the default A record from the example.com zone.
The geographical config in geo.conf might look like this:
www.example.com: - geo: "CZ;Prague" A: 192.0.2.0 TXT: "Prague" - geo: "CZ;Brno" A: 192.0.2.1 TXT: "Brno" - geo: "CZ;*" A: 192.0.2.2 TXT: "Czechia"
In this example we specify different A and TXT records to be returned if the client queries from Prague or from Brno. If a client queries from the Czech Republic but outside of the two cities, the third option applies. The “*” here stands for “any city”. Finally if a client queries from somewhere else, the default record from the zone is returned.
Let’s have a test run
Finally we are ready to run the server! Open up a terminal and type
$ knotd -c /etc/knot/knot.conf
$ systemctl start knot.service
according to you preferences.
You can try querying the server using the kdig utility using the +subnet option to specify different source addresses and check if you are getting the expected responses. The following query invokes the net module instance:
$ kdig @127.0.0.1 A www.example.com +subnet=192.0.2.66 ;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 56869 ;; Flags: qr aa rd; QUERY: 1; ANSWER: 1; AUTHORITY: 0; ADDITIONAL: 1 ;; EDNS PSEUDOSECTION: ;; Version: 0; flags: ; UDP size: 4096 B; ext-rcode: NOERROR ;; CLIENT-SUBNET: 192.0.2.66/32/24 ;; QUESTION SECTION: ;; www.example.com. IN A ;; ANSWER SECTION: www.example.com. 60 IN A 192.0.2.50 ;; Received 72 B ;; Time 2018-09-18 16:47:15 CEST ;; From 127.0.0.1@53(UDP) in 0.1 ms
And the next query invokes the geo module instance:
$ kdig @127.0.0.1 TXT www.example.com +subnet=188.8.131.52 ;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 33440 ;; Flags: qr aa rd; QUERY: 1; ANSWER: 1; AUTHORITY: 0; ADDITIONAL: 1 ;; EDNS PSEUDOSECTION: ;; Version: 0; flags: ; UDP size: 4096 B; ext-rcode: NOERROR ;; CLIENT-SUBNET: 184.108.40.206/32/20 ;; QUESTION SECTION: ;; www.example.com. IN TXT ;; ANSWER SECTION: www.example.com. 60 IN TXT "Brno" ;; Received 110 B ;; Time 2018-09-18 16:49:47 CEST ;; From 127.0.0.1@53(UDP) in 0.3 ms