]> granicus.if.org Git - pdns/commitdiff
GeoIP backend implementation
authorAki Tuomi <cmouse@desteem.org>
Thu, 4 Sep 2014 14:06:09 +0000 (17:06 +0300)
committerAki Tuomi <cmouse@desteem.org>
Tue, 16 Sep 2014 17:59:45 +0000 (20:59 +0300)
31 files changed:
.travis.yml
configure.ac
m4/pdns_with_geo.m4 [new file with mode: 0644]
modules/Makefile.am
modules/geoipbackend/Makefile.am [new file with mode: 0644]
modules/geoipbackend/OBJECTFILES [new file with mode: 0644]
modules/geoipbackend/OBJECTLIBS [new file with mode: 0644]
modules/geoipbackend/geoipbackend.cc [new file with mode: 0644]
modules/geoipbackend/geoipbackend.hh [new file with mode: 0644]
modules/geoipbackend/regression-tests/.gitignore [new file with mode: 0644]
modules/geoipbackend/regression-tests/00dnssec-grabkeys/command [new file with mode: 0755]
modules/geoipbackend/regression-tests/00dnssec-grabkeys/description [new file with mode: 0644]
modules/geoipbackend/regression-tests/00dnssec-grabkeys/expected_result [new file with mode: 0644]
modules/geoipbackend/regression-tests/basic-a-dnssec/command [new file with mode: 0755]
modules/geoipbackend/regression-tests/basic-a-dnssec/description [new file with mode: 0644]
modules/geoipbackend/regression-tests/basic-a-dnssec/expected_result [new file with mode: 0644]
modules/geoipbackend/regression-tests/basic-a-dnssec/skip.nodnssec [new file with mode: 0644]
modules/geoipbackend/regression-tests/basic-a-resolution/command [new file with mode: 0755]
modules/geoipbackend/regression-tests/basic-a-resolution/description [new file with mode: 0644]
modules/geoipbackend/regression-tests/basic-a-resolution/expected_result [new file with mode: 0644]
modules/geoipbackend/regression-tests/region-a-resolution/command [new file with mode: 0755]
modules/geoipbackend/regression-tests/region-a-resolution/description [new file with mode: 0644]
modules/geoipbackend/regression-tests/region-a-resolution/expected_result [new file with mode: 0644]
modules/geoipbackend/regression-tests/static-any-resolution/command [new file with mode: 0755]
modules/geoipbackend/regression-tests/static-any-resolution/description [new file with mode: 0644]
modules/geoipbackend/regression-tests/static-any-resolution/expected_result [new file with mode: 0644]
pdns/docs/pdns.xml
pdns/iputils.hh
regression-tests/backends/common
regression-tests/backends/geoip-master [new file with mode: 0644]
regression-tests/start-test-stop

index 38e9223afe070c2dfed9c384e603bd81cf1ebf33..79f2bf82977aa6a391b5bebda53285143139fd97 100644 (file)
@@ -5,12 +5,13 @@ compiler:
 before_script:
  - git describe --always --dirty=+
  - sudo /sbin/ip addr add 10.0.3.0/24 dev lo
+ - sudo /sbin/ip addr add 1.2.3.4/32 dev lo
  - sudo rm /etc/apt/sources.list.d/travis_ci_zeromq3-source.list
  - sudo apt-get update
- - sudo apt-get install --no-install-recommends libboost-all-dev libtolua-dev bc libcdb-dev libnet-dns-perl unbound-host ldnsutils dnsutils bind9utils libtool libcdb-dev xmlto links asciidoc ruby-json ruby-sqlite3 rubygems libcurl4-openssl-dev ruby1.9.1 socat time libzmq1 libzmq-dev pkg-config daemontools authbind liblua5.1-posix1 libopendbx1-dev libopendbx1-sqlite3 python-virtualenv libldap2-dev softhsm libp11-kit-dev p11-kit moreutils
+ - sudo apt-get install --no-install-recommends libboost-all-dev libtolua-dev bc libcdb-dev libnet-dns-perl unbound-host ldnsutils dnsutils bind9utils libtool libcdb-dev xmlto links asciidoc ruby-json ruby-sqlite3 rubygems libcurl4-openssl-dev ruby1.9.1 socat time libzmq1 libzmq-dev pkg-config daemontools authbind liblua5.1-posix1 libopendbx1-dev libopendbx1-sqlite3 python-virtualenv libldap2-dev softhsm libp11-kit-dev p11-kit moreutils libgeoip-dev geoip-database
  - sudo sh -c 'sed s/precise/trusty/g /etc/apt/sources.list > /etc/apt/sources.list.d/trusty.list'
  - sudo apt-get update
- - sudo apt-get install liblmdb0 liblmdb-dev lmdb-utils
+ - sudo apt-get install liblmdb0 liblmdb-dev lmdb-utils libyaml-cpp-dev 
  - sudo update-alternatives --set ruby /usr/bin/ruby1.9.1
  - sudo touch /etc/authbind/byport/53
  - sudo chmod 755 /etc/authbind/byport/53
@@ -31,7 +32,7 @@ before_script:
  - p11-kit -l # ensure it's ok
 script:
  - ./bootstrap
- - ./configure --with-modules='bind gmysql gpgsql gsqlite3 mydns tinydns remote random opendbx ldap lmdb' --enable-unit-tests --enable-tools --enable-remotebackend-zeromq --enable-experimental-pkcs11
+ - ./configure --with-modules='bind geoip gmysql gpgsql gsqlite3 mydns tinydns remote random opendbx ldap lmdb' --enable-unit-tests --enable-tools --enable-remotebackend-zeromq --enable-experimental-pkcs11
  - make -k dist
  - make -k -j 4
  - make -k install DESTDIR=/tmp/pdns-install-dir
@@ -60,6 +61,7 @@ script:
  - touch tests/verify-dnssec-zone/allow-missing
  - touch tests/verify-dnssec-zone/skip.nsec3 # some (travis) tools in this test are unable to handle nsec3 zones
  - touch tests/verify-dnssec-zone/skip.optout
+ - export geoipregion=oc geoipregionip=1.2.3.4
  - ./timestamp ./start-test-stop 5300 bind-both
  - ./timestamp ./start-test-stop 5300 bind-dnssec-both
  - ./timestamp ./start-test-stop 5300 bind-dnssec-pkcs11
@@ -67,6 +69,8 @@ script:
  - ./timestamp ./start-test-stop 5300 bind-dnssec-nsec3-optout-both
  - ./timestamp ./start-test-stop 5300 bind-dnssec-nsec3-narrow
  - ./timestamp ./start-test-stop 5300 bind-hybrid-nsec3
+ - ./timestamp ./start-test-stop 5300 geoipbackend
+ - ./timestamp ./start-test-stop 5300 geoipbackend-nsec3-narrow
  - ./timestamp ./start-test-stop 5300 gmysql-nodnssec-both
  - ./timestamp ./start-test-stop 5300 gmysql-both
  - ./timestamp ./start-test-stop 5300 gmysql-nsec3-both
index 0669154a7104fe8e252fe852fdacef86b015aef8..e2814b5b48c467a8cfdc3379aa2f9762155b42da 100644 (file)
@@ -63,6 +63,16 @@ eval full_libdir="\"$libdir\""
 # detect pkg-config explicitly
 PKG_PROG_PKG_CONFIG
 
+AC_CHECK_HEADERS(
+       [sys/mman.h],
+       [AC_CHECK_FUNC(
+               [mmap],
+               [AC_DEFINE(HAVE_MMAP, [1], [Define to 1 if you have mmap])],
+               [have_mmap=no]
+       )],
+       [have_mmap=no]
+)
+
 PDNS_CHECK_RAGEL
 AC_CHECK_PROG([ASCIIDOC], [asciidoc], [asciidoc])
 
@@ -284,6 +294,9 @@ for a in $modules $dynmodules; do
     tinydns)
       PDNS_CHECK_CDB
       ;;
+    geoip)
+      PDNS_CHECK_GEOIP
+      ;;
   esac
 done
 
@@ -358,6 +371,7 @@ AC_CONFIG_FILES([
   modules/bindbackend/Makefile
   modules/db2backend/Makefile
   modules/geobackend/Makefile
+  modules/geoipbackend/Makefile
   modules/gmysqlbackend/Makefile
   modules/goraclebackend/Makefile
   modules/gpgsqlbackend/Makefile
diff --git a/m4/pdns_with_geo.m4 b/m4/pdns_with_geo.m4
new file mode 100644 (file)
index 0000000..d0bcb63
--- /dev/null
@@ -0,0 +1,4 @@
+AC_DEFUN([PDNS_CHECK_GEOIP], [
+  PKG_CHECK_MODULES([GEOIP], [geoip])
+  PKG_CHECK_MODULES([YAML], [yaml-cpp >= 0.5])
+])
index 1a544a9fbd81fbe2590acc5d9968b845646b104b..2af3f1799decb694719ad526029ecc80fa629f95 100644 (file)
@@ -1,2 +1,2 @@
 SUBDIRS=@moduledirs@
-DIST_SUBDIRS=bindbackend db2backend geobackend gmysqlbackend goraclebackend gpgsqlbackend gsqlite3backend ldapbackend luabackend mydnsbackend opendbxbackend oraclebackend pipebackend tinydnsbackend remotebackend randombackend lmdbbackend
+DIST_SUBDIRS=bindbackend db2backend geobackend gmysqlbackend goraclebackend gpgsqlbackend gsqlite3backend ldapbackend luabackend mydnsbackend opendbxbackend oraclebackend pipebackend tinydnsbackend remotebackend randombackend lmdbbackend geoipbackend
diff --git a/modules/geoipbackend/Makefile.am b/modules/geoipbackend/Makefile.am
new file mode 100644 (file)
index 0000000..240f4cd
--- /dev/null
@@ -0,0 +1,5 @@
+AM_CPPFLAGS=$(THREADFLAGS) $(BOOST_CPPFLAGS) $(YAML_CFLAGS) $(GEOIP_CFLAGS)
+EXTRA_DIST=OBJECTFILES OBJECTLIBS
+pkglib_LTLIBRARIES = libgeoipbackend.la
+libgeoipbackend_la_SOURCES=geoipbackend.cc geoipbackend.hh
+libgeoipbackend_la_LDFLAGS=-module -avoid-version
diff --git a/modules/geoipbackend/OBJECTFILES b/modules/geoipbackend/OBJECTFILES
new file mode 100644 (file)
index 0000000..7173c16
--- /dev/null
@@ -0,0 +1 @@
+geoipbackend.lo
diff --git a/modules/geoipbackend/OBJECTLIBS b/modules/geoipbackend/OBJECTLIBS
new file mode 100644 (file)
index 0000000..d87d58b
--- /dev/null
@@ -0,0 +1 @@
+$(YAML_LIBS) $(GEOIP_LIBS)
diff --git a/modules/geoipbackend/geoipbackend.cc b/modules/geoipbackend/geoipbackend.cc
new file mode 100644 (file)
index 0000000..3dfc7a6
--- /dev/null
@@ -0,0 +1,650 @@
+#include "geoipbackend.hh"
+#include <sstream>
+#include <regex.h>
+#include <glob.h>
+
+pthread_rwlock_t GeoIPBackend::s_state_lock=PTHREAD_RWLOCK_INITIALIZER;
+
+class GeoIPDomain {
+public:
+  int id;
+  string domain;
+  int ttl;
+  map<string, string> services;
+  map<string, vector<DNSResourceRecord> > records;
+};
+
+static vector<GeoIPDomain> s_domains;
+static GeoIP *s_gi = 0; // geoip database
+static GeoIP *s_gi6 = 0; // geoip database
+static int s_rc = 0; // refcount
+
+GeoIPBackend::GeoIPBackend(const string& suffix) {
+  WriteLock wl(&s_state_lock);
+  d_dnssec = false;
+  setArgPrefix("geoip" + suffix);
+  if (getArg("dnssec-keydir").empty() == false) {
+    DIR *d = opendir(getArg("dnssec-keydir").c_str());
+    if (d == NULL) {
+      throw PDNSException("dnssec-keydir " + getArg("dnssec-keydir") + " does not exist");
+    }
+    d_dnssec = true;
+    closedir(d);
+  }
+  if (s_rc == 0) { // first instance gets to open everything
+    initialize();
+  }
+  d_dbmode = GeoIP_database_edition(s_gi);
+  s_rc++;
+}
+
+void GeoIPBackend::initialize() {
+  YAML::Node config;
+  vector<GeoIPDomain> tmp_domains;
+  GeoIP *gi;
+
+  string mode = getArg("database-cache");
+  int flags;
+  if (mode == "standard") 
+    flags = GEOIP_STANDARD;
+  else if (mode == "memory")
+    flags = GEOIP_MEMORY_CACHE;
+  else if (mode == "index") 
+    flags = GEOIP_INDEX_CACHE;
+#ifdef HAVE_MMAP
+  else if (mode == "mmap")
+    flags = GEOIP_MMAP_CACHE;
+#endif
+  else
+    throw PDNSException("Invalid cache mode " + mode + " for GeoIP backend");
+
+  if (getArg("database-file").empty() == false) {
+    gi = GeoIP_open(getArg("database-file").c_str(), flags);
+    if (gi == NULL)
+      throw PDNSException("Cannot open GeoIP database " + getArg("database-file"));
+    if (s_gi) GeoIP_delete(s_gi);
+    s_gi = gi;
+  }
+  if (getArg("database-file6").empty() == false) {
+    gi = GeoIP_open(getArg("database-file6").c_str(), flags);
+    if (gi == NULL)
+      throw PDNSException("Cannot open GeoIP database " + getArg("database-file6"));
+    if (s_gi6) GeoIP_delete(s_gi6);
+    s_gi6 = gi;
+  }
+
+  if (s_gi == NULL && s_gi6 == NULL) 
+    throw PDNSException("You need to specify one database at least");
+
+  config = YAML::LoadFile(getArg("zones-file"));
+
+  BOOST_FOREACH(YAML::Node domain, config["domains"]) {
+    GeoIPDomain dom;
+    dom.id = s_domains.size();
+    dom.domain = domain["domain"].as<string>();
+    std::transform(dom.domain.begin(), dom.domain.end(), dom.domain.begin(), dns_tolower);
+    dom.ttl = domain["ttl"].as<int>();
+
+    for(YAML::const_iterator recs = domain["records"].begin(); recs != domain["records"].end(); recs++) {
+      string qname = recs->first.as<string>();
+      std::transform(qname.begin(), qname.end(), qname.begin(), dns_tolower);
+      vector<DNSResourceRecord> rrs;
+
+      BOOST_FOREACH(YAML::Node item, recs->second) {
+        YAML::const_iterator rec = item.begin();
+        DNSResourceRecord rr;
+        rr.domain_id = dom.id;
+        rr.ttl = dom.ttl;
+        rr.qname = qname;
+        if (rec->first.IsNull()) {
+          rr.qtype = "NULL";
+        } else {
+          string qtype = boost::to_upper_copy(rec->first.as<string>());
+          rr.qtype = qtype;
+        }
+        if (rec->second.IsNull()) {
+          rr.content = "";
+        } else {
+          string content=rec->second.as<string>();
+          if (rr.qtype == QType::MX || rr.qtype == QType::SRV) {
+            // extract priority
+            rr.priority=atoi(content.c_str());
+            string::size_type pos = content.find_first_not_of("0123456789");
+            if(pos != string::npos)
+               boost::erase_head(content, pos);
+            trim_left(content);
+          }
+          rr.content = content;
+        } 
+                
+        rr.auth = 1;
+        rr.d_place = DNSResourceRecord::ANSWER;
+        rrs.push_back(rr);
+      }
+      std::swap(dom.records[qname], rrs);
+    }
+
+    for(YAML::const_iterator service = domain["services"].begin(); service != domain["services"].end(); service++) {
+      dom.services[service->first.as<string>()] = service->second.as<string>();
+    }
+
+    tmp_domains.push_back(dom);
+  }
+
+  s_domains.clear();
+  std::swap(s_domains, tmp_domains);
+}
+
+GeoIPBackend::~GeoIPBackend() {
+  WriteLock wl(&s_state_lock);
+  s_rc--;
+  if (s_rc == 0) { // last instance gets to cleanup
+    if (s_gi)
+      GeoIP_delete(s_gi);
+    if (s_gi6)
+      GeoIP_delete(s_gi6);
+    s_gi = NULL;
+    s_gi6 = NULL;
+    s_domains.clear();
+  }
+}
+
+void GeoIPBackend::lookup(const QType &qtype, const string &qdomain, DNSPacket *pkt_p, int zoneId) {
+  ReadLock rl(&s_state_lock);
+  GeoIPDomain dom;
+  bool found = false;
+
+  //cerr << qtype.getName() << " " << qdomain << " " << zoneId << std::endl;
+
+  if (d_result.size()>0) 
+    throw PDNSException("Cannot perform lookup while another is running");
+
+  string search = qdomain;
+  std::transform(search.begin(), search.end(), search.begin(), dns_tolower);
+
+  d_result.clear();
+
+  if (zoneId > -1 && zoneId < static_cast<int>(s_domains.size())) 
+    dom = s_domains[zoneId];
+  else {
+    BOOST_FOREACH(GeoIPDomain i, s_domains) {
+      if (endsOn(search, dom.domain)) {
+        dom = i; 
+        found = true;
+        break;
+      }
+    }
+    if (!found) return; // not found
+  }
+
+  if (dom.records.count(search)) { // return static value
+    map<string, vector<DNSResourceRecord> >::iterator i = dom.records.find(search);
+    BOOST_FOREACH(DNSResourceRecord rr, i->second) {
+      if (qtype == QType::ANY || rr.qtype == qtype) {
+        d_result.push_back(rr);
+        d_result.back().qname = qdomain;
+      }
+    }
+    return;
+  }
+
+  if (!(qtype == QType::ANY || qtype == QType::CNAME)) return;
+
+  string ip = "0.0.0.0";
+  bool v6 = false;
+  if (pkt_p != NULL) {
+    ip = pkt_p->getRealRemote().toStringNoMask();
+    v6 = pkt_p->getRealRemote().isIpv6();
+  }
+
+  if (dom.services.count(search) == 0) return; // no hit
+  map<string, string>::const_iterator target = dom.services.find(search);
+  string format = target->second;
+  
+  format = format2str(format, ip, v6);
+
+  DNSResourceRecord rr;
+  rr.domain_id = dom.id;
+  rr.qtype = QType::CNAME;
+  rr.qname = qdomain;
+  rr.content = format;
+  rr.auth = 1;
+  rr.ttl = dom.ttl;
+  rr.scopeMask = (v6 ? 128 : 32);
+  d_result.push_back(rr);
+}
+
+bool GeoIPBackend::get(DNSResourceRecord &r) {
+  if (d_result.empty()) return false;
+
+  r = d_result.back();
+  d_result.pop_back();
+
+  //cerr << "get " << r.qname << " IN " << r.qtype.getName() << " " << r.content << endl;
+
+  return true;
+}
+
+string GeoIPBackend::queryGeoIP(const string &ip, bool v6, GeoIPQueryAttribute attribute) {
+  string ret = "unknown";
+  const char *val = NULL;
+  GeoIPRegion *gir = NULL;
+  GeoIPRecord *gir2 = NULL;
+  int id;
+
+
+  if (v6 && s_gi6) {
+    if (attribute == Afi) {
+      return "v6";
+    } else if (d_dbmode == GEOIP_ISP_EDITION_V6 || d_dbmode == GEOIP_ORG_EDITION_V6) {
+      if (attribute == Name) {
+        val = GeoIP_name_by_addr_v6(s_gi6, ip.c_str());
+      }
+    } else if (d_dbmode == GEOIP_COUNTRY_EDITION_V6 ||
+        d_dbmode == GEOIP_LARGE_COUNTRY_EDITION_V6 ||
+        d_dbmode == GEOIP_COUNTRY_EDITION) {
+      id = GeoIP_id_by_addr_v6(s_gi6, ip.c_str());
+      if (attribute == Country) {
+        val = GeoIP_code3_by_id(id);
+      } else if (attribute == Continent) {
+        val = GeoIP_continent_by_id(id);
+      }
+    } else if (d_dbmode == GEOIP_REGION_EDITION_REV0 ||
+        d_dbmode == GEOIP_REGION_EDITION_REV1) {
+      gir = GeoIP_region_by_addr_v6(s_gi6, ip.c_str());
+      if (gir) {
+        if (attribute == Country) {
+          id = GeoIP_id_by_code(gir->country_code);
+          val = GeoIP_code3_by_id(id);
+        } else if (attribute == Region) {
+          val = gir->region;
+        } else if (attribute == Continent) {
+          id = GeoIP_id_by_code(gir->country_code);
+          val = GeoIP_continent_by_id(id);
+        }
+      }
+    } else if (d_dbmode == GEOIP_CITY_EDITION_REV0_V6 ||
+               d_dbmode == GEOIP_CITY_EDITION_REV1_V6) {
+      gir2 = GeoIP_record_by_addr_v6(s_gi6, ip.c_str());
+      if (gir2) {
+        if (attribute == Country) {
+          val = gir2->country_code3;
+        } else if (attribute == Region) {
+          val = gir2->region;
+        } else if (attribute == Continent) {
+          id = GeoIP_id_by_code(gir2->country_code);
+          val = GeoIP_continent_by_id(id);
+        } else if (attribute == City) {
+          val = gir2->city;
+        }
+      }
+    }
+  } else if (!v6 && s_gi) {
+    if (attribute == Afi) {
+      return "v4";
+    } else if (d_dbmode == GEOIP_ISP_EDITION || d_dbmode == GEOIP_ORG_EDITION) {
+      if (attribute == Name) {
+        val = GeoIP_name_by_addr_v6(s_gi, ip.c_str());
+      }
+    } else if (d_dbmode == GEOIP_COUNTRY_EDITION ||
+        d_dbmode == GEOIP_LARGE_COUNTRY_EDITION) {
+      id = GeoIP_id_by_addr(s_gi, ip.c_str());
+      if (attribute == Country) {
+        val = GeoIP_code3_by_id(id);
+      } else if (attribute == Continent) {
+        val = GeoIP_continent_by_id(id);
+      }
+    } else if (d_dbmode == GEOIP_REGION_EDITION_REV0 ||
+        d_dbmode == GEOIP_REGION_EDITION_REV1) {
+      gir = GeoIP_region_by_addr(s_gi, ip.c_str());
+      if (gir) {
+        if (attribute == Country) {
+          id = GeoIP_id_by_code(gir->country_code);
+          val = GeoIP_code3_by_id(id);
+        } else if (attribute == Region) {
+          val = gir->region;
+        } else if (attribute == Continent) {
+          id = GeoIP_id_by_code(gir->country_code);
+          val = GeoIP_continent_by_id(id);
+        }
+      }
+    } else if (d_dbmode == GEOIP_CITY_EDITION_REV0 ||
+               d_dbmode == GEOIP_CITY_EDITION_REV1) {
+      gir2 = GeoIP_record_by_addr(s_gi, ip.c_str());
+      if (gir2) {
+        if (attribute == Country) {
+          val = gir2->country_code3;
+        } else if (attribute == Region) {
+          val = gir2->region;
+        } else if (attribute == Continent) {
+          id = GeoIP_id_by_code(gir2->country_code);
+          val = GeoIP_continent_by_id(id);
+        } else if (attribute == City) {
+          val = gir2->city;
+        }
+      }
+    }
+  }
+  if (val) {
+    ret = val;
+    if (ret == "--") ret = "unknown";
+    std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower);
+  }
+  return ret;
+}
+
+string GeoIPBackend::format2str(string format, const string& ip, bool v6) {
+  string::size_type cur,last;
+  GeoIPQueryAttribute attr;
+  last=0;
+  while((cur = format.find("%", last)) != string::npos) {
+    if (!format.compare(cur,3,"%co")) {
+      attr = Country;
+    } else if (!format.compare(cur,3,"%cn")) {
+      attr = Continent;
+    } else if (!format.compare(cur,3,"%af")) {
+      attr = Afi;
+    } else if (!format.compare(cur,3,"%re")) {
+      attr = Region;
+    } else if (!format.compare(cur,3,"%na")) {
+      attr = Name;
+    } else if (!format.compare(cur,3,"%ci")) {
+      attr = City;
+    } else if (!format.compare(cur,2,"%%")) {
+      last = cur + 2; continue; 
+    } else { 
+      last = cur + 1; continue; 
+    }
+
+    string rep = queryGeoIP(ip, v6, attr);
+
+    format.replace(cur, 3, rep);
+    last = cur + rep.size(); // move to next attribute
+  }
+  return format;
+}
+
+void GeoIPBackend::reload() {
+  WriteLock wl(&s_state_lock);
+  
+  try {
+    initialize();
+  } catch (PDNSException &pex) {
+    L<<Logger::Error<<"GeoIP backend reload failed: " << pex.reason << endl;
+  } catch (std::exception &stex) {
+    L<<Logger::Error<<"GeoIP backend reload failed: " << stex.what() << endl;
+  } catch (...) {
+    L<<Logger::Error<<"GeoIP backend reload failed" << endl;
+  }
+}
+
+void GeoIPBackend::rediscover(string* status) {
+  reload();
+}
+
+bool GeoIPBackend::getDomainInfo(const string &domain, DomainInfo &di) {
+  ReadLock rl(&s_state_lock);
+  cerr << "looking for " << domain << endl;
+
+  BOOST_FOREACH(GeoIPDomain dom, s_domains) {
+    if (pdns_iequals(dom.domain, domain)) {
+      SOAData sd;
+      this->getSOA(domain, sd);
+      di.id = dom.id;
+      di.zone = dom.domain;
+      di.serial = sd.serial;
+      di.kind = DomainInfo::Native;
+      di.backend = this;
+      return true;
+    }
+  }
+  return false;
+}
+
+bool GeoIPBackend::getAllDomainMetadata(const string& name, std::map<std::string, std::vector<std::string> >& meta) {
+  if (!d_dnssec) return false;
+
+  ReadLock rl(&s_state_lock);
+  BOOST_FOREACH(GeoIPDomain dom, s_domains) {
+    if (pdns_iequals(dom.domain, name)) {
+      if (hasDNSSECkey(dom.domain)) {
+        meta[string("NSEC3NARROW")].push_back("1");
+        meta[string("NSEC3PARAM")].push_back("1 0 1 f95a");
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+bool GeoIPBackend::getDomainMetadata(const std::string& name, const std::string& kind, std::vector<std::string>& meta) {
+  if (!d_dnssec) return false;
+
+  ReadLock rl(&s_state_lock);
+  BOOST_FOREACH(GeoIPDomain dom, s_domains) {
+    if (pdns_iequals(dom.domain, name)) {
+      if (hasDNSSECkey(dom.domain)) {
+        if (kind == "NSEC3NARROW")
+          meta.push_back(string("1"));
+        if (kind == "NSEC3PARAM")
+          meta.push_back(string("1 0 1 f95a"));
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+bool GeoIPBackend::getDomainKeys(const std::string& name, unsigned int kind, std::vector<DNSBackend::KeyData>& keys) {
+  if (!d_dnssec) return false;
+  ReadLock rl(&s_state_lock);
+  BOOST_FOREACH(GeoIPDomain dom, s_domains) {
+    if (pdns_iequals(dom.domain, name)) {
+      regex_t reg;
+      regmatch_t regm[5];
+      regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE|REG_EXTENDED);
+      ostringstream pathname;
+      pathname << getArg("dnssec-keydir") << "/" << dom.domain << "*.key";
+      glob_t glob_result;
+      if (glob(pathname.str().c_str(),GLOB_ERR,NULL,&glob_result) == 0) {
+        for(size_t i=0;i<glob_result.gl_pathc;i++) {
+          if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
+            DNSBackend::KeyData kd;
+            kd.id = atoi(glob_result.gl_pathv[i]+regm[3].rm_so);
+            kd.active = atoi(glob_result.gl_pathv[i]+regm[4].rm_so);
+            kd.flags = atoi(glob_result.gl_pathv[i]+regm[2].rm_so);
+            ifstream ifs(glob_result.gl_pathv[i]);
+            ostringstream content;
+            char buffer[1024];
+            while(ifs.good()) {
+              ifs.read(buffer, sizeof buffer);
+              if (ifs.gcount()>0) {
+                content << string(buffer, ifs.gcount());
+              }
+            }
+            ifs.close();
+            kd.content = content.str();
+            keys.push_back(kd);
+          }
+        }
+      }
+      regfree(&reg);
+      globfree(&glob_result);
+      return true;
+    }
+  }
+  return false;
+}
+
+bool GeoIPBackend::removeDomainKey(const string& name, unsigned int id) {
+  if (!d_dnssec) return false;
+  WriteLock rl(&s_state_lock);
+  ostringstream path;
+
+  BOOST_FOREACH(GeoIPDomain dom, s_domains) {
+    if (pdns_iequals(dom.domain, name)) {
+      regex_t reg;
+      regmatch_t regm[5];
+      regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE|REG_EXTENDED);
+      ostringstream pathname;
+      pathname << getArg("dnssec-keydir") << "/" << dom.domain << "*.key";
+      glob_t glob_result;
+      if (glob(pathname.str().c_str(),GLOB_ERR,NULL,&glob_result) == 0) {
+        for(size_t i=0;i<glob_result.gl_pathc;i++) {
+          if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
+            unsigned int kid = atoi(glob_result.gl_pathv[i]+regm[3].rm_so);
+            if (kid == id) {
+              if (unlink(glob_result.gl_pathv[i])) {
+                cerr << "Cannot delete key:" << strerror(errno) << endl;
+              }
+              break;
+            }
+          }
+        }
+      }
+      regfree(&reg);
+      globfree(&glob_result);
+      return true;
+    }
+  }
+  return false;
+}
+
+int GeoIPBackend::addDomainKey(const string& name, const KeyData& key) {
+  if (!d_dnssec) return false;
+  WriteLock rl(&s_state_lock);
+  int nextid=1;
+
+  BOOST_FOREACH(GeoIPDomain dom, s_domains) {
+    if (pdns_iequals(dom.domain, name)) {
+      regex_t reg;
+      regmatch_t regm[5];
+      regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE|REG_EXTENDED);
+      ostringstream pathname;
+      pathname << getArg("dnssec-keydir") << "/" << dom.domain << "*.key";
+      glob_t glob_result;
+      if (glob(pathname.str().c_str(),GLOB_ERR,NULL,&glob_result) == 0) {
+        for(size_t i=0;i<glob_result.gl_pathc;i++) {
+          if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
+            int kid = atoi(glob_result.gl_pathv[i]+regm[3].rm_so);
+            if (kid >= nextid) nextid = kid+1;
+          }
+        }
+      }
+      regfree(&reg);
+      globfree(&glob_result);
+      pathname.str("");
+      pathname << getArg("dnssec-keydir") << "/" << dom.domain << "." << key.flags << "." << nextid << "." << (key.active?"1":"0") << ".key";
+      ofstream ofs(pathname.str().c_str());
+      ofs.write(key.content.c_str(), key.content.size());
+      ofs.close();
+      return nextid;
+    }
+  }
+  return false;
+
+}
+
+bool GeoIPBackend::activateDomainKey(const string& name, unsigned int id) {
+  if (!d_dnssec) return false;
+  WriteLock rl(&s_state_lock);
+  BOOST_FOREACH(GeoIPDomain dom, s_domains) {
+    if (pdns_iequals(dom.domain, name)) {
+      regex_t reg;
+      regmatch_t regm[5];
+      regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE|REG_EXTENDED);
+      ostringstream pathname;
+      pathname << getArg("dnssec-keydir") << "/" << dom.domain << "*.key";
+      glob_t glob_result;
+      if (glob(pathname.str().c_str(),GLOB_ERR,NULL,&glob_result) == 0) {
+        for(size_t i=0;i<glob_result.gl_pathc;i++) {
+          if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
+            unsigned int kid = atoi(glob_result.gl_pathv[i]+regm[3].rm_so);
+            if (kid == id && atoi(glob_result.gl_pathv[i]+regm[4].rm_so) == 0) {
+              ostringstream newpath; 
+              newpath << getArg("dnssec-keydir") << "/" << dom.domain << "." << atoi(glob_result.gl_pathv[i]+regm[2].rm_so) << "." << kid << ".1.key";
+              if (rename(glob_result.gl_pathv[i], newpath.str().c_str())) {
+                cerr << "Cannot active key: " << strerror(errno) << endl;
+              }
+            }
+          }
+        }
+      }
+      globfree(&glob_result);
+      regfree(&reg); 
+      return true;
+    }
+  }
+  return false;
+}
+
+bool GeoIPBackend::deactivateDomainKey(const string& name, unsigned int id) {
+  if (!d_dnssec) return false;
+  WriteLock rl(&s_state_lock);
+  BOOST_FOREACH(GeoIPDomain dom, s_domains) {
+    if (pdns_iequals(dom.domain, name)) {
+      regex_t reg;
+      regmatch_t regm[5];
+      regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE|REG_EXTENDED);
+      ostringstream pathname;
+      pathname << getArg("dnssec-keydir") << "/" << dom.domain << "*.key";
+      glob_t glob_result;
+      if (glob(pathname.str().c_str(),GLOB_ERR,NULL,&glob_result) == 0) {
+        for(size_t i=0;i<glob_result.gl_pathc;i++) {
+          if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
+            unsigned int kid = atoi(glob_result.gl_pathv[i]+regm[3].rm_so);
+            if (kid == id && atoi(glob_result.gl_pathv[i]+regm[4].rm_so) == 1) {
+              ostringstream newpath;
+              newpath << getArg("dnssec-keydir") << "/" << dom.domain << "." << atoi(glob_result.gl_pathv[i]+regm[2].rm_so) << "." << kid << ".0.key";
+              if (rename(glob_result.gl_pathv[i], newpath.str().c_str())) {
+                cerr << "Cannot deactive key: " << strerror(errno) << endl;
+              }
+            }
+          }
+        }
+      }
+      globfree(&glob_result);
+      regfree(&reg);
+      return true;
+    }
+  }
+  return false;
+}
+
+bool GeoIPBackend::hasDNSSECkey(const string& name) {
+  ostringstream pathname;
+  pathname << getArg("dnssec-keydir") << "/" << name << "*.key";
+  glob_t glob_result;
+  if (glob(pathname.str().c_str(),GLOB_ERR,NULL,&glob_result) == 0) {
+    globfree(&glob_result);
+    return true;
+  }
+  return false;
+}
+
+class GeoIPFactory : public BackendFactory{
+public:
+  GeoIPFactory() : BackendFactory("geoip") {}
+
+  void declareArguments(const string &suffix = "") {
+    declare(suffix, "zones-file", "YAML file to load zone(s) configuration", "");
+    declare(suffix, "database-file6", "File to load IPv6 geoip data from", "/usr/share/GeoIP/GeoIPv6.dat");
+    declare(suffix, "database-file", "File to load IPv4 geoip data from", "/usr/share/GeoIP/GeoIP.dat");
+    declare(suffix, "database-cache", "Cache mode (standard, memory, index, mmap)", "standard");
+    declare(suffix, "dnssec-keydir", "Directory to hold dnssec keys (also turns DNSSEC on)", "");
+  }
+
+  DNSBackend *make(const string &suffix) {
+    return new GeoIPBackend(suffix);
+  }
+};
+
+class GeoIPLoader {
+public:
+  GeoIPLoader() {
+    BackendMakers().report(new GeoIPFactory);
+    L << Logger::Info << "[geobackend] This is the geo backend version " VERSION " (" __DATE__ ", " __TIME__ ") reporting" << endl;
+  }
+};
+
+static GeoIPLoader geoloader;
diff --git a/modules/geoipbackend/geoipbackend.hh b/modules/geoipbackend/geoipbackend.hh
new file mode 100644 (file)
index 0000000..0bb8560
--- /dev/null
@@ -0,0 +1,66 @@
+#include "config.h"
+#include "pdns/namespaces.hh"
+
+#include <vector>
+#include <map>
+#include <string>
+#include <fstream>
+#include <yaml-cpp/yaml.h>
+#include <pthread.h>
+#include <boost/foreach.hpp>
+#include <GeoIP.h>
+#include <GeoIPCity.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "pdns/dnspacket.hh"
+#include "pdns/dns.hh"
+#include "pdns/dnsbackend.hh"
+#include "pdns/lock.hh"
+
+class GeoIPDomain;
+
+class GeoIPBackend: public DNSBackend {
+public:
+  GeoIPBackend(const std::string& suffix="");
+  ~GeoIPBackend();
+
+  virtual void lookup(const QType &qtype, const string &qdomain, DNSPacket *pkt_p=0, int zoneId=-1);
+  virtual bool list(const string &target, int domain_id, bool include_disabled=false) { return false; } // not supported
+  virtual bool get(DNSResourceRecord &r);
+  virtual void reload();
+  virtual void rediscover(string *status = 0);
+  virtual bool getDomainInfo(const string &domain, DomainInfo &di);
+
+  // dnssec support
+  virtual bool doesDNSSEC() { return d_dnssec; };
+  virtual bool getAllDomainMetadata(const string& name, std::map<std::string, std::vector<std::string> >& meta);
+  virtual bool getDomainMetadata(const std::string& name, const std::string& kind, std::vector<std::string>& meta);
+  virtual bool getDomainKeys(const std::string& name, unsigned int kind, std::vector<DNSBackend::KeyData>& keys);
+  virtual bool removeDomainKey(const string& name, unsigned int id);
+  virtual int addDomainKey(const string& name, const KeyData& key);
+  virtual bool activateDomainKey(const string& name, unsigned int id);
+  virtual bool deactivateDomainKey(const string& name, unsigned int id);
+
+  enum GeoIPQueryAttribute {
+    Afi,
+    City,
+    Continent,
+    Country,
+    Name,
+    Region
+  };
+
+private:
+  static pthread_rwlock_t s_state_lock;
+
+  void initialize();
+  void ip2geo(const GeoIPDomain& dom, const string& qname, const string& ip);
+  string queryGeoIP(const string &ip, bool v6, GeoIPQueryAttribute attribute);
+  string format2str(string format, const string& ip, bool v6);  
+  int d_dbmode;
+  bool d_dnssec; 
+  bool hasDNSSECkey(const string &domain);
+
+  vector<DNSResourceRecord> d_result;
+};
diff --git a/modules/geoipbackend/regression-tests/.gitignore b/modules/geoipbackend/regression-tests/.gitignore
new file mode 100644 (file)
index 0000000..638f32c
--- /dev/null
@@ -0,0 +1,4 @@
+diff
+real_result
+*.out
+geosec
diff --git a/modules/geoipbackend/regression-tests/00dnssec-grabkeys/command b/modules/geoipbackend/regression-tests/00dnssec-grabkeys/command
new file mode 100755 (executable)
index 0000000..0d984d3
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/sh -e
+set pipefail
+rm -f trustedkeys
+rm -f unbound-host.conf
+for zone in example.com 
+do
+       drill -p $port -o rd -D dnskey $zone @$nameserver | grep -v '^;' | grep -v AwEAAarTiHhPgvD28WCN8UBXcEcf8f >> trustedkeys
+       echo "stub-zone:" >> unbound-host.conf
+       echo "  name: $zone" >> unbound-host.conf
+       echo "  stub-addr: $nameserver@$port" >> unbound-host.conf
+       echo "" >> unbound-host.conf
+done
+
+echo "server:" >> unbound-host.conf
+echo "  do-not-query-address: 192.168.0.0/16" >> unbound-host.conf
+echo '  trust-anchor-file: "trustedkeys"' >> unbound-host.conf
diff --git a/modules/geoipbackend/regression-tests/00dnssec-grabkeys/description b/modules/geoipbackend/regression-tests/00dnssec-grabkeys/description
new file mode 100644 (file)
index 0000000..4315650
--- /dev/null
@@ -0,0 +1 @@
+Grab DNSKEY records for validation testing.
diff --git a/modules/geoipbackend/regression-tests/00dnssec-grabkeys/expected_result b/modules/geoipbackend/regression-tests/00dnssec-grabkeys/expected_result
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/modules/geoipbackend/regression-tests/basic-a-dnssec/command b/modules/geoipbackend/regression-tests/basic-a-dnssec/command
new file mode 100755 (executable)
index 0000000..f67a854
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+cleandig www.geo.example.com A dnssec
diff --git a/modules/geoipbackend/regression-tests/basic-a-dnssec/description b/modules/geoipbackend/regression-tests/basic-a-dnssec/description
new file mode 100644 (file)
index 0000000..6cd423e
--- /dev/null
@@ -0,0 +1 @@
+Basic DNSSEC test
diff --git a/modules/geoipbackend/regression-tests/basic-a-dnssec/expected_result b/modules/geoipbackend/regression-tests/basic-a-dnssec/expected_result
new file mode 100644 (file)
index 0000000..63dad9c
--- /dev/null
@@ -0,0 +1,7 @@
+0      unknown.service.geo.example.com.        IN      A       30      127.0.0.1
+0      unknown.service.geo.example.com.        IN      RRSIG   30      A 8 5 30 [expiry] [inception] [keytag] geo.example.com. ...
+0      www.geo.example.com.    IN      CNAME   30      unknown.service.geo.example.com.
+0      www.geo.example.com.    IN      RRSIG   30      CNAME 8 4 30 [expiry] [inception] [keytag] geo.example.com. ...
+2      .       IN      OPT     32768   
+Rcode: 0, RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='www.geo.example.com.', qtype=A
diff --git a/modules/geoipbackend/regression-tests/basic-a-dnssec/skip.nodnssec b/modules/geoipbackend/regression-tests/basic-a-dnssec/skip.nodnssec
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/modules/geoipbackend/regression-tests/basic-a-resolution/command b/modules/geoipbackend/regression-tests/basic-a-resolution/command
new file mode 100755 (executable)
index 0000000..c8e28a0
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+cleandig www.geo.example.com A
+
diff --git a/modules/geoipbackend/regression-tests/basic-a-resolution/description b/modules/geoipbackend/regression-tests/basic-a-resolution/description
new file mode 100644 (file)
index 0000000..51497ac
--- /dev/null
@@ -0,0 +1,2 @@
+This test tries to resolve a straight A record that is directly available in
+the database.
diff --git a/modules/geoipbackend/regression-tests/basic-a-resolution/expected_result b/modules/geoipbackend/regression-tests/basic-a-resolution/expected_result
new file mode 100644 (file)
index 0000000..eef51b2
--- /dev/null
@@ -0,0 +1,4 @@
+0      unknown.service.geo.example.com.        IN      A       30      127.0.0.1
+0      www.geo.example.com.    IN      CNAME   30      unknown.service.geo.example.com.
+Rcode: 0, RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='www.geo.example.com.', qtype=A
diff --git a/modules/geoipbackend/regression-tests/region-a-resolution/command b/modules/geoipbackend/regression-tests/region-a-resolution/command
new file mode 100755 (executable)
index 0000000..432a2e9
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+nameserver=$geoipregionip cleandig www.geo.example.com A
diff --git a/modules/geoipbackend/regression-tests/region-a-resolution/description b/modules/geoipbackend/regression-tests/region-a-resolution/description
new file mode 100644 (file)
index 0000000..51497ac
--- /dev/null
@@ -0,0 +1,2 @@
+This test tries to resolve a straight A record that is directly available in
+the database.
diff --git a/modules/geoipbackend/regression-tests/region-a-resolution/expected_result b/modules/geoipbackend/regression-tests/region-a-resolution/expected_result
new file mode 100644 (file)
index 0000000..dc3ff19
--- /dev/null
@@ -0,0 +1,4 @@
+0      oc.service.geo.example.com.     IN      A       30      62.236.200.4
+0      www.geo.example.com.    IN      CNAME   30      oc.service.geo.example.com.
+Rcode: 0, RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='www.geo.example.com.', qtype=A
diff --git a/modules/geoipbackend/regression-tests/static-any-resolution/command b/modules/geoipbackend/regression-tests/static-any-resolution/command
new file mode 100755 (executable)
index 0000000..abd0fdf
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+cleandig geo.example.com ANY
+
diff --git a/modules/geoipbackend/regression-tests/static-any-resolution/description b/modules/geoipbackend/regression-tests/static-any-resolution/description
new file mode 100644 (file)
index 0000000..51497ac
--- /dev/null
@@ -0,0 +1,2 @@
+This test tries to resolve a straight A record that is directly available in
+the database.
diff --git a/modules/geoipbackend/regression-tests/static-any-resolution/expected_result b/modules/geoipbackend/regression-tests/static-any-resolution/expected_result
new file mode 100644 (file)
index 0000000..4b35164
--- /dev/null
@@ -0,0 +1,6 @@
+0      geo.example.com.        IN      MX      30      10 mx.example.com.
+0      geo.example.com.        IN      NS      30      ns1.example.com.
+0      geo.example.com.        IN      NS      30      ns2.example.com.
+0      geo.example.com.        IN      SOA     30      ns1.example.com. hostmaster.example.com. 2014090125 7200 3600 1209600 3600
+Rcode: 0, RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='geo.example.com.', qtype=ANY
index a13134aa333cfdfdcf813f9f26d453b5faa52b50..f4ff4ccee68eb3b24c516da3abb4f80b6394aa3a 100644 (file)
@@ -21441,6 +21441,132 @@ VALUES (:zoneid, :ip)
        <filename>modules/geobackend/README</filename>, part of the PowerDNS Authoritative Server distribution.
       </para>
     </sect1>
+    <sect1 id="geoip"><title>GeoIP backend</title>
+      <para>
+        <table>
+          <title>GeoIP backend capabilities</title>
+          <tgroup cols="2">
+            <tbody>
+              <row><entry>Native</entry><entry>Yes</entry></row>
+              <row><entry>Master</entry><entry>No</entry></row>
+              <row><entry>Slave</entry><entry>No</entry></row>
+              <row><entry>Superslave</entry><entry>No</entry></row>
+              <row><entry>Autoserial</entry><entry>No</entry></row>
+              <row><entry>DNSSEC</entry><entry>Yes</entry></row>
+            </tbody>
+          </tgroup>
+        </table>
+      </para>
+      <para>
+        The GeoIP backend can be used to distribute queries globally using an MaxMind IP-address/country mapping table, currently avaible for debian and ubuntu for free. Other formats
+        are not yet supported but will be in the future. The only format supported at the moment is country listing. 
+      </para>
+      <para>
+        This allows visitors to be sent to a server close to them, with no appreciable delay, as would otherwise be incurred with a protocol level redirect.
+        Additionally, the Geo Backend can be used to provide service over several clusters, any of which can be taken out of use easily, for example
+        for maintenance purposes.
+      </para>
+      <sect2 id="geoipbackend-prerequisites"><title>Prerequisites</title>
+        <para>
+         To compile the backend, you need libyaml-cpp 0.5 or later and libgeoip. 
+        </para>
+        <para>
+          You must have geoip database available. As of writing, on debian/ubuntu systems, you can use apt-get install geoip-database to get one, and the backend is
+          configured to use the location where these files are installed as source. On other systems you might need to alter the database-file and database-file6 attribute.
+          If you don't need ipv4 or ipv6 support, set the respective setting to "". Leaving it unset leaves it pointing to default location, preventing the software from 
+          starting up. 
+        </para>
+      </sect2>
+      <sect2 id="geoipbackend-parameters"><title>Configuration Parameters</title>
+        <para>
+          These are the configuration file parameters that are available for the GeoIP backend. geoip-zones-files is the only thing you must set, if the defaults suite you.
+          <variablelist>
+            <varlistentry>
+              <term>geoip-database-file</term>
+              <listitem>
+                <para>Specifies the full path of the data file for IPv4 to use.</para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>geoip-database-file6</term>
+              <listitem>
+                <para>Specifies the full path of the data file for IPv6 to use.</para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>geoip-zones-file</term>
+              <listitem>
+                <para>Specifies the full path of the zone configuration file to use.</para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>geoip-dnssec-keydir</term>
+              <listitem>
+                <para>Specifies the full path of a directory that will contain DNSSEC keys.</para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+        </para>
+      </sect2>
+      <sect2 id="geoipbackend-zonefile"><title>Zonefile format</title>
+        <para>
+          Zone configuration file uses YAML syntax. Here is simple example. Note that the &dash; before certain keys is part of the syntax.
+          <programlisting>
+domains:
+- domain: geo.example.com
+  ttl: 30
+  records:
+    geo.example.com:
+      - soa: ns1.example.com hostmaster.example.com 2014090125 7200 3600 1209600 3600
+      - ns: ns1.example.com
+      - ns: ns2.example.com
+      - mx: 10 mx.example.com
+    fin.eu.service.geo.example.com:
+      - a: 62.236.200.4
+      - txt: hello world
+  services:
+    service.geo.example.com: '%co.%cn.service.geo.example.com'
+          </programlisting>
+        </para>
+
+        <para>
+          Keys explained
+          <variablelist>
+            <varlistentry>
+              <term>domains</term>
+              <listitem>
+                <para>Mandatory root key. All configuration is below this</para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>domain</term>
+              <listitem>
+                <para>Defines a domain. You need ttl, records, services under this.</para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>ttl</term>
+              <listitem>
+                <para>TTL value for all records</para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>records</term>
+              <listitem>
+                <para>Put fully qualified name as subkey, under which you must define at least soa: key. Note that this is an array of records, so &dash; is needed for the values.</para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>services</term>
+              <listitem>
+                <para>Defines one or more services for querying. The format supports following placeholders, %% = %, %co = 3-letter country, %cn = continent, %af = v4 or v6. There are also other specifiers that will only work with suitable database and currently are untested. These are %re = region, %na = Name (such as, organisation), %ci = City.</para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+        </para>
+      </sect2>
+    </sect1>
+
  <sect1 id="luabackend"><title>Lua Backend</title>
       <para>
        <warning>
index 6b15f4b2134bc814d184397161e839f0a838a9f9..9d0aa937588d7351db478af96cbcc696bd23c8c5 100644 (file)
@@ -304,6 +304,14 @@ public:
   {
     return d_bits;
   }
+  bool isIpv6() const 
+  {
+    return d_network.sin6.sin6_family == AF_INET6;
+  }
+  bool isIpv4() const
+  {
+    return d_network.sin4.sin_family == AF_INET;
+  }
 private:
   ComboAddress d_network;
   uint32_t d_mask;
index d5d8bf930b678fec28b3cc0c604f6d54bf7ccceb..6beec129a40f9c76d9e82bfcc8c8cb61725adcc8 100644 (file)
@@ -4,7 +4,9 @@ start_master ()
                        bind*)
                                source ./backends/bind-master
                                ;;
-
+                       geoip*)
+                               source ./backends/geoip-master
+                               ;;
                        gmysql*)
                                source ./backends/gmysql-master
                                ;;
diff --git a/regression-tests/backends/geoip-master b/regression-tests/backends/geoip-master
new file mode 100644 (file)
index 0000000..703a520
--- /dev/null
@@ -0,0 +1,77 @@
+case $context in
+       geoipbackend|geoipbackend-nsec3-narrow)
+               set +e
+                if test "x$geoipregion" = "x"; then
+                        echo "This test suite requires that you provide geoipregion which defines the region name produced by MaxMind database with geoipregionip address."
+                        exit 1
+                fi
+                if test "x$geoipregionip" = "x"; then
+                        echo "This test suite requires that you have IP address bound to localhost interface and exported as variable geoipregionip"
+                        exit 1
+                fi
+               ping -q -c1 -W1 -t1 -Ilo $geoipregionip 2>&1 >/dev/null 
+               if test $? -ne 0; then
+                       echo "This test suite requires that you have $geoipregionip bound to localhost interface"
+                       exit 1
+               fi
+               set -e
+                testsdir=../modules/geoipbackend/regression-tests/
+               if test "$context" = "geoipbackend-nsec3-narrow"; then
+                  narrow="narrow"
+                  extracontexts="dnssec nsec3 narrow"
+                  skipreasons="narrow nsec3 nodyndns"
+                  geoipdosec="yes"
+                  geoipkeydir="geoip-dnssec-keydir=$testsdir/geosec"
+                  rm -rf $testsdir/geosec
+                  mkdir -p $testsdir/geosec
+                else
+                  skipreasons="nonarrow nonsec3 nodyndns nodnssec"
+                fi
+               cat > $testsdir/geo.yaml <<EOF
+domains:
+- domain: geo.example.com
+  ttl: 30
+  records:
+    geo.example.com:
+      - soa: ns1.example.com hostmaster.example.com 2014090125 7200 3600 1209600 3600
+      - ns: ns1.example.com
+      - ns: ns2.example.com
+      - mx: 10 mx.example.com
+    $geoipregion.service.geo.example.com:
+      - a: 62.236.200.4
+    unknown.service.geo.example.com:
+      - a: 127.0.0.1
+  services:
+    www.geo.example.com: '%cn.service.geo.example.com'
+EOF
+               cat > $testsdir/region-a-resolution/expected_result <<EOF
+0      $geoipregion.service.geo.example.com.   IN      A       30      62.236.200.4
+0      www.geo.example.com.    IN      CNAME   30      $geoipregion.service.geo.example.com.
+Rcode: 0, RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='www.geo.example.com.', qtype=A
+EOF
+
+               # generate pdns.conf for pdnssec
+               cat > pdns-geoip.conf <<EOF
+launch=geoip
+geoip-zones-file=$testsdir/geo.yaml
+EOF
+
+               if [ "$geoipdosec" = "yes" ]
+               then
+                       echo "$geoipkeydir" >> pdns-geoip.conf
+                       ../pdns/pdnssec --config-dir=. --config-name=geoip secure-zone geo.example.com
+                       geoipkeydir="--geoip-dnssec-keydir=$testsdir/geosec"
+               fi
+
+               $RUNWRAPPER $PDNS --daemon=no --local-port=$port --socket-dir=./ \
+                       --no-shuffle --launch=geoip \
+                       --cache-ttl=$cachettl --experimental-dname-processing --no-config \
+                       --send-root-referral --distributor-threads=1 \
+                        --geoip-zones-file=$testsdir/geo.yaml \
+                       $geoipkeydir &
+               ;;
+
+       *)
+               nocontext=yes
+esac
index 6b4f2f30a9710221f616f0f269ea86d972ef010f..0c300922763d69cee5ccd1b8f991acbf16dba7f8 100755 (executable)
@@ -180,6 +180,7 @@ Usage: ./start-test-stop <port> [<context>] [wait|nowait] [<cachettl>] [<specifi
 
 context is one of:
 bind bind-dnssec bind-dnssec-nsec3 bind-dnssec-nsec3-optout bind-dnssec-nsec3-narrow
+geoipbackend geoipbackend-nsec3-narrow
 gmysql-nodnssec gmysql gmysql-nsec3 gmysql-nsec3-optout gmysql-nsec3-narrow
 goracle-nodnssec goracle goracle-nsec3 goracle-nsec3-optout goracle-nsec3-narrow
 gpgsql-nodnssec gpgsql gpgsql-nsec3 gpgsql-nsec3-optout gpgsql-nsec3-narrow