How to Implement GeoDNS/Geolocation-Based DNS Routing on Linux Servers


2 views

Many distributed applications require traffic routing based on user location. Commercial DNS providers like UltraDNS offer this as a managed service, but when you need full control over your infrastructure, self-hosting becomes essential.

Here are three proven solutions for Linux servers:

1. PowerDNS with GeoIP Backend
2. BIND with GeoIP Patch
3. NSD with Lua Scripting

This configuration routes US traffic to 174.1.1.1 and EU traffic to 65.2.2.2:

# powerdns.conf
launch=geoip
geoip-database-files=/usr/share/GeoIP/GeoIP.dat
geoip-zones-file=/etc/powerdns/geo-zones.conf
# geo-zones.conf
x.com. {
    geoip-us=A 174.1.1.1;
    geoip-eu=A 65.2.2.2;
    default=A 192.0.2.1;
}

The BIND GeoIP patch extends BIND 9's functionality:

# named.conf
view "us" {
    match-clients { country_US; };
    zone "x.com" {
        type master;
        file "/etc/bind/db.x.com.us";
    };
};

view "eu" {
    match-clients { country_EU; };
    zone "x.com" {
        type master;
        file "/etc/bind/db.x.com.eu";
    };
};

When implementing GeoDNS:

  • Use mmdb format for faster GeoIP lookups
  • Consider Edge DNS caching
  • Monitor query response times

Verify with dig using different source IPs:

dig @your_dns_server x.com +client=1.2.3.4  # US IP
dig @your_dns_server x.com +client=5.6.7.8  # EU IP

Geo-DNS (or Directional DNS) automatically routes users to the nearest server based on their geographic location. Commercial services like UltraDNS and Comwired offer this feature, but many developers prefer self-hosted solutions for better control and cost efficiency.

Here are three robust options for implementing Geo-DNS on Linux:

1. PowerDNS with GeoIP Backend

PowerDNS offers a GeoIP backend that enables geographic routing:

# Install PowerDNS and GeoIP backend
sudo apt-get install pdns-server pdns-backend-geoip

# Sample PowerDNS configuration (pdns.conf)
launch=geoip
geoip-database-files=/usr/share/GeoIP/GeoIP.dat,/usr/share/GeoIP/GeoIPCity.dat
geoip-zones-file=/etc/powerdns/geo.zones

Example zone file (/etc/powerdns/geo.zones):

example.com.    IN  SOA ns1.example.com. admin.example.com. ( ... )
example.com.    IN  NS  ns1.example.com.
example.com.    IN  A   192.0.2.1  ; Default
example.com.    US  A   198.51.100.1
example.com.    EU  A   203.0.113.1

2. Bind9 with GeoIP ACLs

Bind9 can implement geographic routing using GeoIP ACLs:

# Install Bind9 and GeoIP support
sudo apt-get install bind9 bind9-utils libgeoip-dev

# Configure named.conf.options
acl "us_clients" {
    geoip_country US;
};
acl "eu_clients" {
    geoip_country DE,FR,UK,IT,ES;
};

view "us_view" {
    match-clients { us_clients; };
    zone "example.com" {
        type master;
        file "/etc/bind/zones/db.example.com.us";
    };
};

view "eu_view" {
    match-clients { eu_clients; };
    zone "example.com" {
        type master;
        file "/etc/bind/zones/db.example.com.eu";
    };
};

3. CoreDNS with GeoIP Plugin

CoreDNS offers a lightweight alternative with its GeoIP plugin:

# Corefile configuration
. {
    geoip {
        dbpath "/GeoLite2-Country.mmdb"
        default_ip 192.0.2.1
        rules {
            country us 198.51.100.1
            country eu 203.0.113.1
        }
    }
    forward . 8.8.8.8
}

When implementing Geo-DNS:

  • Use MaxMind's GeoLite2 databases for accurate IP geolocation
  • Consider caching TTL values carefully (300-600 seconds is typical)
  • Monitor query latency - GeoIP lookups add overhead
  • Implement EDNS Client Subnet (ECS) for better accuracy with CDNs

For a complete solution combining PowerDNS and Redis for dynamic updates:

# Python script to update records dynamically
import redis
import geoip2.database

r = redis.StrictRedis()
reader = geoip2.database.Reader('/GeoLite2-City.mmdb')

def update_geo_ip(client_ip, server_ip):
    try:
        response = reader.city(client_ip)
        country = response.country.iso_code
        r.hset(f'geo:{country}', client_ip, server_ip)
    except Exception as e:
        print(f"Error updating GeoIP: {e}")

# Sample usage
update_geo_ip('203.0.113.45', '198.51.100.1')