Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

geoipbackend: Support reading zones from directory #11597

Merged
merged 6 commits into from Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions configure.ac
Expand Up @@ -40,6 +40,7 @@ LT_INIT([disable-static dlopen])
PDNS_CHECK_OS
PTHREAD_SET_NAME
AC_FUNC_STRERROR_R
AX_CXX_CXXFS

PDNS_WITH_LUA([mandatory])
PDNS_CHECK_LUA_HPP
Expand Down Expand Up @@ -308,6 +309,10 @@ LDFLAGS="$RELRO_LDFLAGS $LDFLAGS"
CFLAGS="$PIE_CFLAGS $CFLAGS"
CXXFLAGS="$PIE_CFLAGS $CXXFLAGS"
PROGRAM_LDFLAGS="$PIE_LDFLAGS $PROGRAM_LDFLAGS"
AS_IF([test "$ax_cxx_cv_filesystem_lib" != "none"],
[PROGRAM_LDFLAGS="$PROGRAM_LDFLAGS -l$ax_cxx_cv_filesystem_lib"],
[]
)
AC_SUBST([PROGRAM_LDFLAGS])

PDNS_ENABLE_COVERAGE
Expand Down
1 change: 1 addition & 0 deletions docs/backends/geoip.rst
Expand Up @@ -164,6 +164,7 @@ Keys explained
format (e.g. %cc).
:custom_mapping: Defines the mapping between the lookup format and a custom value to replace ``%mp`` placeholder.

:zones_dir: Directory to load zones from. Each file must contain exactly one ``zone:`` object, formatted like individual domains in the example configuration above.
:mapping_lookup_formats: Same as per domain, but used as default value if not defined at the domain level.
:custom_mapping: Same as per domain, but used as default value if not defined at the domain level.

Expand Down
31 changes: 31 additions & 0 deletions m4/ax_cxx_fs.m4
@@ -0,0 +1,31 @@
AC_DEFUN([AX_CXX_CXXFS], [
AC_LANG_PUSH([C++])
old_LIBS="$LIBS"
dnl * Test first if it can be used without anything, then -lstdc++fs and -lc++fs
AC_CACHE_CHECK([for library with std::filesystem], [ax_cxx_cv_filesystem_lib], [
ax_cxx_cv_filesystem_lib=none
AC_LINK_IFELSE([AC_LANG_PROGRAM(
[[#include <iostream>
#include <filesystem>]],
[[std::filesystem::path path(".");
std::filesystem::status(path);]])],
[], [
LIBS="$LIBS -lstdc++fs"
AC_LINK_IFELSE([AC_LANG_PROGRAM(
[[#include <iostream>
#include <filesystem>]],
[[std::filesystem::path path(".");
std::filesystem::status(path);]])],
[ax_cxx_cv_filesystem_lib=stdc++fs], [
LIBS="$old_LIBS -lc++fs"
AC_LINK_IFELSE([AC_LANG_PROGRAM(
[[#include <iostream>
#include <filesystem>]],
[[std::filesystem::path path(".");
std::filesystem::status(path);]])],
[ax_cxx_cv_filesystem_lib=c++fs], [AC_MSG_ERROR([Cannot find std::filesystem library])])
])])
LIBS="$old_LIBS"
])
AC_LANG_POP()
])
130 changes: 84 additions & 46 deletions modules/geoipbackend/geoipbackend.cc
Expand Up @@ -31,6 +31,7 @@
#include <boost/algorithm/string/replace.hpp>
#include <boost/format.hpp>
#include <fstream>
#include <filesystem>
#include <yaml-cpp/yaml.h>

ReadWriteLock GeoIPBackend::s_state_lock;
Expand Down Expand Up @@ -120,50 +121,9 @@ static bool validateMappingLookupFormats(const vector<string>& formats)
return true;
}

void GeoIPBackend::initialize()
bool GeoIPBackend::loadDomain(const YAML::Node& domain, unsigned int id, GeoIPDomain& dom)
{
YAML::Node config;
vector<GeoIPDomain> tmp_domains;

s_geoip_files.clear(); // reset pointers

if (getArg("database-files").empty() == false) {
vector<string> files;
stringtok(files, getArg("database-files"), " ,\t\r\n");
for (auto const& file : files) {
s_geoip_files.push_back(GeoIPInterface::makeInterface(file));
}
}

if (s_geoip_files.empty())
g_log << Logger::Warning << "No GeoIP database files loaded!" << endl;

if (!getArg("zones-file").empty()) {
try {
config = YAML::LoadFile(getArg("zones-file"));
}
catch (YAML::Exception& ex) {
throw PDNSException(string("Cannot read config file ") + ex.msg);
}
}

// Global lookup formats and mapping will be used
// if none defined at the domain level.
vector<string> global_mapping_lookup_formats;
map<std::string, std::string> global_custom_mapping;
if (YAML::Node formats = config["mapping_lookup_formats"]) {
global_mapping_lookup_formats = formats.as<vector<string>>();
if (!validateMappingLookupFormats(global_mapping_lookup_formats))
throw PDNSException(string("%mp is not allowed in mapping lookup"));
}
if (YAML::Node mapping = config["custom_mapping"]) {
global_custom_mapping = mapping.as<map<std::string, std::string>>();
}

for (YAML::const_iterator _domain = config["domains"].begin(); _domain != config["domains"].end(); _domain++) {
const auto& domain = *_domain;
GeoIPDomain dom;
dom.id = tmp_domains.size();
try {
dom.domain = DNSName(domain["domain"].as<string>());
dom.ttl = domain["ttl"].as<int>();

Expand Down Expand Up @@ -275,13 +235,13 @@ void GeoIPBackend::initialize()
dom.mapping_lookup_formats = mapping_lookup_formats;
}
else {
dom.mapping_lookup_formats = global_mapping_lookup_formats;
dom.mapping_lookup_formats = d_global_mapping_lookup_formats;
}
if (YAML::Node mapping = domain["custom_mapping"]) {
dom.custom_mapping = mapping.as<map<std::string, std::string>>();
}
else {
dom.custom_mapping = global_custom_mapping;
dom.custom_mapping = d_global_custom_mapping;
}

dom.services[srvName].netmask4 = netmask4;
Expand Down Expand Up @@ -362,10 +322,88 @@ void GeoIPBackend::initialize()
}
}
}
}
catch (std::exception& ex) {
g_log << Logger::Error << ex.what() << endl;
return false;
}
catch (PDNSException& ex) {
g_log << Logger::Error << ex.reason << endl;
return false;
}
return true;
}

tmp_domains.push_back(std::move(dom));
void GeoIPBackend::loadDomainsFromDirectory(const std::string& dir, vector<GeoIPDomain>& domains)
{
vector<std::filesystem::path> paths;
for (const std::filesystem::path& p : std::filesystem::directory_iterator(std::filesystem::path(dir)))
if (std::filesystem::is_regular_file(p) && p.has_extension() && (p.extension() == ".yaml" || p.extension() == ".yml"))
paths.push_back(p);
std::sort(paths.begin(), paths.end());
for (const auto& p : paths) {
try {
GeoIPDomain dom;
const auto& zoneRoot = YAML::LoadFile(p.string());
// expect zone key
const auto& zone = zoneRoot["zone"];
if (loadDomain(zone, domains.size(), dom))
domains.push_back(dom);
}
catch (std::exception& ex) {
g_log << Logger::Warning << "Cannot load zone from " << p << ": " << ex.what() << endl;
}
}
}

void GeoIPBackend::initialize()
{
YAML::Node config;
vector<GeoIPDomain> tmp_domains;

s_geoip_files.clear(); // reset pointers

if (getArg("database-files").empty() == false) {
vector<string> files;
stringtok(files, getArg("database-files"), " ,\t\r\n");
for (auto const& file : files) {
s_geoip_files.push_back(GeoIPInterface::makeInterface(file));
}
}

if (s_geoip_files.empty())
g_log << Logger::Warning << "No GeoIP database files loaded!" << endl;

if (!getArg("zones-file").empty()) {
try {
config = YAML::LoadFile(getArg("zones-file"));
}
catch (YAML::Exception& ex) {
throw PDNSException(string("Cannot read config file ") + ex.msg);
}
}

// Global lookup formats and mapping will be used
// if none defined at the domain level.
if (YAML::Node formats = config["mapping_lookup_formats"]) {
d_global_mapping_lookup_formats = formats.as<vector<string>>();
if (!validateMappingLookupFormats(d_global_mapping_lookup_formats))
throw PDNSException(string("%mp is not allowed in mapping lookup"));
}
if (YAML::Node mapping = config["custom_mapping"]) {
d_global_custom_mapping = mapping.as<map<std::string, std::string>>();
}

for (YAML::const_iterator _domain = config["domains"].begin(); _domain != config["domains"].end(); _domain++) {
GeoIPDomain dom;
auto id = tmp_domains.size();
if (loadDomain(*_domain, id, dom))
tmp_domains.push_back(std::move(dom));
}

if (YAML::Node domain_dir = config["zones_dir"])
loadDomainsFromDirectory(domain_dir.as<string>(), tmp_domains);

s_domains.clear();
std::swap(s_domains, tmp_domains);

Expand Down
9 changes: 9 additions & 0 deletions modules/geoipbackend/geoipbackend.hh
Expand Up @@ -36,6 +36,11 @@

class GeoIPInterface;

namespace YAML
{
class Node;
};

struct GeoIPDomain;

struct GeoIPNetmask
Expand Down Expand Up @@ -77,6 +82,10 @@ private:
bool d_dnssec;
bool hasDNSSECkey(const DNSName& name);
bool lookup_static(const GeoIPDomain& dom, const DNSName& search, const QType& qtype, const DNSName& qdomain, const Netmask& addr, GeoIPNetmask& gl);
bool loadDomain(const YAML::Node& domain, unsigned int id, GeoIPDomain& dom);
void loadDomainsFromDirectory(const std::string& dir, vector<GeoIPDomain>& domains);
vector<DNSResourceRecord> d_result;
vector<GeoIPInterface> d_files;
std::vector<std::string> d_global_mapping_lookup_formats;
std::map<std::string, std::string> d_global_custom_mapping;
};
20 changes: 20 additions & 0 deletions regression-tests/backends/geoip-master
Expand Up @@ -76,6 +76,26 @@ domains:
mapping_lookup_formats: ['%cn']
custom_mapping:
$geoipregion: earth
EOF
if ! [ -d $testsdir/geozones ]; then
mkdir $testsdir/geozones
fi
cat > $testsdir/geozones/geo2.yaml <<EOF
zone:
domain: geo2.example.com
ttl: 30
records:
geo2.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
moon.map.geo2.example.com:
- txt: "overridden moon mapping"
services:
map.geo2.example.com: '%mp.map.geo2.example.com'
custom_mapping:
$geoipregion: moon
EOF
cat > $testsdir/region-a-resolution/expected_result <<EOF
0 www.geo.example.com. 30 IN A $geoipregionip
Expand Down