[Pdns-dev] Small code change to support split-horizon - GEO Routing using SQL

Chris Savery chrissavery at gmail.com
Tue Sep 2 00:29:50 CEST 2008


I have been experimenting with some (very) small changes to provide a 
function I desired - Geo Based DNS Routing. I'm very new to the PDNS 
code and just started yesterday browsing the source. I realize there is 
a GeoBackend available but it doesn't really do what I want. I want to 
also have my DNS records in MySql and be able to use a web console to 
manage them easily and assign regions that way.

So I will give an explanation of what I've done here and if anyone is 
interested please feel free to ask for more. I haven't been reading the 
list so I'm not aware of other mods to accomplish this. I did do some 
googling though and didn't see anything. I think this is a pretty nifty 
little mod to accomplish quite a bit of additional flexibility. In fact, 
I was looking at Wikipedia and they have a DNS server comparison chart 
that indicates PowerDNS could do but doesn't currently have "split 
horizon" support. I believe this would provide a very simple version of 
that. So here goes...

My idea was that the only thing that we really need was to get the IP of 
the source request into the SQL query and then the rest could be handled 
there by returning suitable results. I looked at how PDNS code was 
structured and it's very nice. The GSQL backend is the parent of the 
other more specific SQL backends so making a very small change there 
enables this capability for all of them.

I modified gsqlbackend.cc so that the 4 main query builder statements  
using sprintf have an additional parameter. Normally the sql query 
doesn't see this parameter because it by default only contains 2 %s 
substitution variables. However, if you provide a custom set of queries 
in the pdns.conf file then you can use another %s that will be replaced 
with the IP address of your source request.

The code change is trivial - just add pkt_p->getRemote.c_str() after the 
2 or 3 other parameters to the sprintf on lines: 260, 267, 280, 287 - 
sorry I have not made  a patch to do this (yet). This change shouldn't 
affect normal use with default queries.

Now, if you add your own custom queries then you can use them to do GEO 
based routing with SQL as described below. I think you could also do 
other stuff fairly easily too but these are the ones I'm using for what 
I need.

First - I have added a region_id (int) to the records table. This allows 
me to assign any record to be used only for a certain region or if zero 
(0) then it will be valid for any region. Next, I created a couple more 
tables to provide the simple GEO backend. You can get away with one 
table but by having two you can support multiple mappings of regions to 
domain records. For example, if you want to have multiple customers in 
the DNS records and they have different ways of mapping country codes to 
their own servers. So this first table could easily be skipped if 
multiple mappings aren't needed.

table - geomap
region_id - int -- this is what maps countries to the regions used in 
the records table
geo_id - int -- this is same as below, country code

table - cidrmap
cidr unsigned int -- this is an unsigned int version of the start of 
each ip block
geo_id - int  -- this is a numeric equivalent to the 2 letter country code

Ok. Now I wrote a small php script to take the Maxmind country data and 
import it into the cidr table. It stores the IP address as an unsigned 
int in acsending order such that each value represents a change in 
assigned block. So if your ip value is greater than one number (but 
below the next) then that means it's in this block. This makes it very 
fast an easy to locate the block for any IP. The result of the lookup is 
a geo_id code that represents the country (or whatever region division 
the cidr corresponds to).

The geo_id code could be used directly to match against the records 
table. But if you allow for one level of indirection by stepping it 
through the geomap table first then you can have multiple mappings of 
country to DNS records. Also this allows you to map many countries into 
one region_id that connects to one server DNS entry. It's very easy and 
fast. Just need to fill in country data to cidrmap table (my php script 
does that) and then any user can select countries and map them to DNS 
zones which would fill in the entries of geomap.

So to make this work you need to mod the default queries with a bit of 
extra joining and stuff. Here's an example that works for me (you add 
this to pdns.conf along with same idea for 3 other queries):

gmysql-basic-query=select content,ttl,prio,type,domain_id,name from 
records r, geomap g where r.type='%s' and r.name='%s' and 
((r.region_id=g.region_id and g.geo_id=(select geo_id from cidrmap where 
cidr <= inet_aton('%s') order by cidr desc limit 1)) or r.region_id=0)

Notice here that the mysql function inet_aton takes a text dotted ip 
value and returns an unsigned int value. Just the magic needed.

The other queries are modified likewise. In short, we select the geo_id 
for the cidr block that the ip is in by finding the ones less or equal 
and flipping them upside down and taking the first only. Then we join 
that id value to the geomap table and get the region_id. We then use 
that to select only suitable DNS records. At the end we also have a term 
( or r.region_id=0 ) that gets any records with zero region. These ones 
are available to all regions or if no region specific records exist for 
the query.

I haven't had the time to test this a lot. But it does work for my first 
test cases and it even appears to be <almost> as fast as the the default 
queries. That is, when I use the host to test it reports the times to 
get results as being only slightly more, eg. 4 ms instead of 3ms, or 
some such value.

The Maxmind country data that I imported ends up with about 106,000 
records in the cidrmap table taking about 780K total (and another 1MB 
for indexing too). This isn't too bad and mysql will only load some 
portions into memory so I haven't noticed much increase in PDNS memory 
footprint. The geomap table uses about 190 records to map each country 
to a few regions. Finer granularity could be obtained by adding more 
cidrmap records as needed. The Maxmind citysource  data is about 15 
times bigger (100MB). I haven't tried that. Other source data could be 
used. you could add code to ping back the source IP and build a table of 
times on the fly for each requestor and link that to regions.

Now, finally - I 'm sure there will be an issue with the packet cache in 
a situation like this. So this is the question I have for developers 
here who are much more familiar with PDNS. I think that probably the 
packetcache.cc file can be modified slightly such that source IP value 
is part of the cache key. This would cache the packets such that wrong 
ones aren't given out to other requesters (from different geo regions). 
I intuitively think it's as simple as that or just turning off the cache 
(which seems a real shame). I was going to work on making that change 
tonight and testing if that was enough. Then I thought that probably 
someone here really knows if that will work as expected. And what about 
the query cache? I haven't even started to look at that but maybe it 
stores the queries so that they already get keyed correctly? What else 
may need to be fixed for this to work correctly?

Anyway, I'm open to feedback on this whole thing. Is there some reason 
it will fail miserably? It is working as expected for my very small 
tests here. I set up 3 regions for each of US, EU and AS and then tried 
requests that returned correct A records for the region when the source 
IP matched countries in the region. Not thorough but a first start. If 
anyone wants more details or the php code to import the cidrmap table 
then please go ahead and ask. Should I make up a patch for this? I 
haven't done that before but it could be fun to learn. It would add an 
exciting new functionality to PDNS.

Thanks for your time and I hope this helps someone or at least 
stimulates thought. Who have thunk it was so easy to get Geo mapping out 
of PDNS...

Chris :)

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://mailman.powerdns.com/pipermail/pdns-dev/attachments/20080901/4649b9fb/attachment.htm

More information about the Pdns-dev mailing list