2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 #include "geoipbackend.hh"
27 #include "geoipinterface.hh"
28 #include "pdns/dns_random.hh"
32 #include <boost/algorithm/string/replace.hpp>
33 #include <boost/format.hpp>
37 #pragma GCC diagnostic push
38 #pragma GCC diagnostic ignored "-Wshadow"
39 #include <yaml-cpp/yaml.h>
40 #pragma GCC diagnostic pop
42 ReadWriteLock
GeoIPBackend::s_state_lock
;
44 struct GeoIPDNSResourceRecord
: DNSResourceRecord
52 NetmaskTree
<vector
<string
>> masks
;
53 unsigned int netmask4
;
54 unsigned int netmask6
;
62 map
<DNSName
, GeoIPService
> services
;
63 map
<DNSName
, vector
<GeoIPDNSResourceRecord
>> records
;
64 vector
<string
> mapping_lookup_formats
;
65 map
<std::string
, std::string
> custom_mapping
;
68 static vector
<GeoIPDomain
> s_domains
;
69 static int s_rc
= 0; // refcount - always accessed under lock
71 const static std::array
<string
, 7> GeoIP_WEEKDAYS
= {"mon", "tue", "wed", "thu", "fri", "sat", "sun"};
72 const static std::array
<string
, 12> GeoIP_MONTHS
= {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"};
74 /* So how does it work - we have static records and services. Static records "win".
75 We also insert empty non terminals for records and services.
77 If a service makes an internal reference to a domain also hosted within geoip, we give a direct
78 answers, no CNAMEs involved.
80 If the reference is external, we spoof up a CNAME, and good luck with that
85 /* using a deleter instead of decltype(&closedir) has two big advantages:
86 - the deleter is included in the type and does not have to be passed
87 when creating a new object (easier to use, less memory usage, in theory
89 - we avoid the annoying "ignoring attributes on template argument ‘int (*)(DIR*)’"
90 warning from the compiler, which is there because closedir is tagged as __nonnull((1))
92 void operator()(DIR* dirPtr
) const noexcept
98 using UniqueDirPtr
= std::unique_ptr
<DIR, DirPtrDeleter
>;
100 GeoIPBackend::GeoIPBackend(const string
& suffix
)
102 WriteLock
writeLock(&s_state_lock
);
103 setArgPrefix("geoip" + suffix
);
104 if (!getArg("dnssec-keydir").empty()) {
105 auto dirHandle
= UniqueDirPtr(opendir(getArg("dnssec-keydir").c_str()));
107 throw PDNSException("dnssec-keydir " + getArg("dnssec-keydir") + " does not exist");
111 if (s_rc
== 0) { // first instance gets to open everything
117 static vector
<std::unique_ptr
<GeoIPInterface
>> s_geoip_files
;
119 string
getGeoForLua(const std::string
& ip
, int qaint
);
120 static string
queryGeoIP(const Netmask
& addr
, GeoIPInterface::GeoIPQueryAttribute attribute
, GeoIPNetmask
& gl
);
122 // validateMappingLookupFormats validates any custom format provided by the
123 // user does not use the custom mapping placeholder again, else it would do an
124 // infinite recursion.
125 static bool validateMappingLookupFormats(const vector
<string
>& formats
)
127 string::size_type cur
= 0;
128 string::size_type last
= 0;
130 for (const auto& lookupFormat
: formats
) {
132 while ((cur
= lookupFormat
.find("%", last
)) != string::npos
) {
133 if (lookupFormat
.compare(cur
, 3, "%mp") == 0) {
137 if (lookupFormat
.compare(cur
, 2, "%%") == 0) { // Ensure escaped % is also accepted
141 last
= cur
+ 1; // move to next attribute
147 static vector
<GeoIPDNSResourceRecord
> makeDNSResourceRecord(GeoIPDomain
& dom
, DNSName name
)
149 GeoIPDNSResourceRecord resourceRecord
;
150 resourceRecord
.domain_id
= dom
.id
;
151 resourceRecord
.ttl
= dom
.ttl
;
152 resourceRecord
.qname
= std::move(name
);
153 resourceRecord
.qtype
= QType(0); // empty non terminal
154 resourceRecord
.content
= "";
155 resourceRecord
.auth
= true;
156 resourceRecord
.weight
= 100;
157 resourceRecord
.has_weight
= false;
158 vector
<GeoIPDNSResourceRecord
> rrs
;
159 rrs
.push_back(resourceRecord
);
163 void GeoIPBackend::setupNetmasks(const YAML::Node
& domain
, GeoIPDomain
& dom
)
165 for (auto service
= domain
["services"].begin(); service
!= domain
["services"].end(); service
++) {
166 unsigned int netmask4
= 0;
167 unsigned int netmask6
= 0;
168 DNSName serviceName
{service
->first
.as
<string
>()};
169 NetmaskTree
<vector
<string
>> netmaskTree
;
171 // if it's an another map, we need to iterate it again, otherwise we just add two root entries.
172 if (service
->second
.IsMap()) {
173 for (auto net
= service
->second
.begin(); net
!= service
->second
.end(); net
++) {
174 vector
<string
> value
;
175 if (net
->second
.IsSequence()) {
176 value
= net
->second
.as
<vector
<string
>>();
179 value
.push_back(net
->second
.as
<string
>());
181 if (net
->first
.as
<string
>() == "default") {
182 netmaskTree
.insert(Netmask("0.0.0.0/0")).second
.assign(value
.begin(), value
.end());
183 netmaskTree
.insert(Netmask("::/0")).second
.swap(value
);
186 Netmask netmask
{net
->first
.as
<string
>()};
187 netmaskTree
.insert(netmask
).second
.swap(value
);
188 if (netmask
.isIPv6() && netmask6
< netmask
.getBits()) {
189 netmask6
= netmask
.getBits();
191 if (!netmask
.isIPv6() && netmask4
< netmask
.getBits()) {
192 netmask4
= netmask
.getBits();
198 vector
<string
> value
;
199 if (service
->second
.IsSequence()) {
200 value
= service
->second
.as
<vector
<string
>>();
203 value
.push_back(service
->second
.as
<string
>());
205 netmaskTree
.insert(Netmask("0.0.0.0/0")).second
.assign(value
.begin(), value
.end());
206 netmaskTree
.insert(Netmask("::/0")).second
.swap(value
);
209 // Allow per domain override of mapping_lookup_formats and custom_mapping.
210 // If not defined, the global values will be used.
211 if (YAML::Node formats
= domain
["mapping_lookup_formats"]) {
212 auto mapping_lookup_formats
= formats
.as
<vector
<string
>>();
213 if (!validateMappingLookupFormats(mapping_lookup_formats
)) {
214 throw PDNSException(string("%mp is not allowed in mapping lookup formats of domain ") + dom
.domain
.toLogString());
217 dom
.mapping_lookup_formats
= std::move(mapping_lookup_formats
);
220 dom
.mapping_lookup_formats
= d_global_mapping_lookup_formats
;
222 if (YAML::Node mapping
= domain
["custom_mapping"]) {
223 dom
.custom_mapping
= mapping
.as
<map
<std::string
, std::string
>>();
226 dom
.custom_mapping
= d_global_custom_mapping
;
229 dom
.services
[serviceName
].netmask4
= netmask4
;
230 dom
.services
[serviceName
].netmask6
= netmask6
;
231 dom
.services
[serviceName
].masks
.swap(netmaskTree
);
235 bool GeoIPBackend::loadDomain(const YAML::Node
& domain
, domainid_t domainID
, GeoIPDomain
& dom
)
239 dom
.domain
= ZoneName(domain
["domain"].as
<string
>());
240 dom
.ttl
= domain
["ttl"].as
<int>();
242 for (auto recs
= domain
["records"].begin(); recs
!= domain
["records"].end(); recs
++) {
243 ZoneName qname
= ZoneName(recs
->first
.as
<string
>());
244 vector
<GeoIPDNSResourceRecord
> rrs
;
246 for (auto item
= recs
->second
.begin(); item
!= recs
->second
.end(); item
++) {
247 auto rec
= item
->begin();
248 GeoIPDNSResourceRecord rr
;
249 rr
.domain_id
= dom
.id
;
251 rr
.qname
= qname
.operator const DNSName
&();
252 if (rec
->first
.IsNull()) {
256 string qtype
= boost::to_upper_copy(rec
->first
.as
<string
>());
259 rr
.has_weight
= false;
261 if (rec
->second
.IsNull()) {
264 else if (rec
->second
.IsMap()) {
265 for (YAML::const_iterator iter
= rec
->second
.begin(); iter
!= rec
->second
.end(); iter
++) {
266 auto attr
= iter
->first
.as
<string
>();
267 if (attr
== "content") {
268 auto content
= iter
->second
.as
<string
>();
269 rr
.content
= std::move(content
);
271 else if (attr
== "weight") {
272 rr
.weight
= iter
->second
.as
<int>();
273 if (rr
.weight
<= 0) {
274 g_log
<< Logger::Error
<< "Weight must be positive for " << rr
.qname
<< endl
;
275 throw PDNSException(string("Weight must be positive for ") + rr
.qname
.toLogString());
277 rr
.has_weight
= true;
279 else if (attr
== "ttl") {
280 rr
.ttl
= iter
->second
.as
<int>();
283 g_log
<< Logger::Error
<< "Unsupported record attribute " << attr
<< " for " << rr
.qname
<< endl
;
284 throw PDNSException(string("Unsupported record attribute ") + attr
+ string(" for ") + rr
.qname
.toLogString());
289 auto content
= rec
->second
.as
<string
>();
290 rr
.content
= std::move(content
);
296 std::swap(dom
.records
[qname
.operator const DNSName
&()], rrs
);
299 setupNetmasks(domain
, dom
);
301 // rectify the zone, first static records
302 for (auto& item
: dom
.records
) {
303 // ensure we have parent in records
304 DNSName name
= item
.first
;
305 while (name
.chopOff() && name
.isPartOf(dom
.domain
)) {
306 if (dom
.records
.find(name
) == dom
.records
.end() && (dom
.services
.count(name
) == 0U)) { // don't ENT out a service!
307 auto rrs
= makeDNSResourceRecord(dom
, name
);
308 std::swap(dom
.records
[name
], rrs
);
314 for (auto& item
: dom
.services
) {
315 // ensure we have parent in records
316 DNSName name
= item
.first
;
317 while (name
.chopOff() && name
.isPartOf(dom
.domain
)) {
318 if (dom
.records
.find(name
) == dom
.records
.end()) {
319 auto rrs
= makeDNSResourceRecord(dom
, name
);
320 std::swap(dom
.records
[name
], rrs
);
325 // finally fix weights
326 for (auto& item
: dom
.records
) {
327 map
<uint16_t, float> weights
;
328 map
<uint16_t, float> sums
;
329 map
<uint16_t, GeoIPDNSResourceRecord
*> lasts
;
330 bool has_weight
= false;
331 // first we look for used weight
332 for (const auto& resourceRecord
: item
.second
) {
333 weights
[resourceRecord
.qtype
.getCode()] += static_cast<float>(resourceRecord
.weight
);
334 if (resourceRecord
.has_weight
) {
339 // put them back as probabilities and values..
340 for (auto& resourceRecord
: item
.second
) {
341 uint16_t rr_type
= resourceRecord
.qtype
.getCode();
342 resourceRecord
.weight
= static_cast<int>((static_cast<float>(resourceRecord
.weight
) / weights
[rr_type
]) * 1000.0);
343 sums
[rr_type
] += static_cast<float>(resourceRecord
.weight
);
344 resourceRecord
.has_weight
= has_weight
;
345 lasts
[rr_type
] = &resourceRecord
;
347 // remove rounding gap
348 for (auto& x
: lasts
) {
349 float sum
= sums
[x
.first
];
351 x
.second
->weight
+= (1000 - sum
);
357 catch (std::exception
& ex
) {
358 g_log
<< Logger::Error
<< ex
.what() << endl
;
361 catch (PDNSException
& ex
) {
362 g_log
<< Logger::Error
<< ex
.reason
<< endl
;
368 void GeoIPBackend::loadDomainsFromDirectory(const std::string
& dir
, vector
<GeoIPDomain
>& domains
)
370 vector
<std::filesystem::path
> paths
;
371 for (const std::filesystem::path
& p
: std::filesystem::directory_iterator(std::filesystem::path(dir
))) {
372 if (std::filesystem::is_regular_file(p
) && p
.has_extension() && (p
.extension() == ".yaml" || p
.extension() == ".yml")) {
376 std::sort(paths
.begin(), paths
.end());
377 for (const auto& p
: paths
) {
380 const auto& zoneRoot
= YAML::LoadFile(p
.string());
382 const auto& zone
= zoneRoot
["zone"];
383 if (loadDomain(zone
, domains
.size(), dom
)) {
384 domains
.push_back(dom
);
387 catch (std::exception
& ex
) {
388 g_log
<< Logger::Warning
<< "Cannot load zone from " << p
<< ": " << ex
.what() << endl
;
393 void GeoIPBackend::initialize()
396 vector
<GeoIPDomain
> tmp_domains
;
398 s_geoip_files
.clear(); // reset pointers
400 if (getArg("database-files").empty() == false) {
401 vector
<string
> files
;
402 stringtok(files
, getArg("database-files"), " ,\t\r\n");
403 for (auto const& file
: files
) {
404 s_geoip_files
.push_back(GeoIPInterface::makeInterface(file
));
408 if (s_geoip_files
.empty()) {
409 g_log
<< Logger::Warning
<< "No GeoIP database files loaded!" << endl
;
412 std::string zonesFile
{getArg("zones-file")};
413 if (!zonesFile
.empty()) {
415 config
= YAML::LoadFile(zonesFile
);
417 catch (YAML::Exception
& ex
) {
418 std::string description
{};
419 if (!ex
.mark
.is_null()) {
420 description
= "Configuration error in " + zonesFile
+ ", line " + std::to_string(ex
.mark
.line
+ 1) + ", column " + std::to_string(ex
.mark
.column
+ 1);
423 description
= "Cannot read config file " + zonesFile
;
425 throw PDNSException(description
+ ": " + ex
.msg
);
429 // Global lookup formats and mapping will be used
430 // if none defined at the domain level.
431 if (YAML::Node formats
= config
["mapping_lookup_formats"]) {
432 d_global_mapping_lookup_formats
= formats
.as
<vector
<string
>>();
433 if (!validateMappingLookupFormats(d_global_mapping_lookup_formats
)) {
434 throw PDNSException(string("%mp is not allowed in mapping lookup"));
437 if (YAML::Node mapping
= config
["custom_mapping"]) {
438 d_global_custom_mapping
= mapping
.as
<map
<std::string
, std::string
>>();
441 for (YAML::const_iterator _domain
= config
["domains"].begin(); _domain
!= config
["domains"].end(); _domain
++) {
443 if (loadDomain(*_domain
, tmp_domains
.size(), dom
)) {
444 tmp_domains
.push_back(std::move(dom
));
448 if (YAML::Node domain_dir
= config
["zones_dir"]) {
449 loadDomainsFromDirectory(domain_dir
.as
<string
>(), tmp_domains
);
453 std::swap(s_domains
, tmp_domains
);
455 extern std::function
<std::string(const std::string
& ip
, int)> g_getGeo
;
456 g_getGeo
= getGeoForLua
;
459 GeoIPBackend::~GeoIPBackend()
462 WriteLock
writeLock(&s_state_lock
);
464 if (s_rc
== 0) { // last instance gets to cleanup
465 s_geoip_files
.clear();
473 bool GeoIPBackend::lookup_static(const GeoIPDomain
& dom
, const DNSName
& search
, const QType
& qtype
, const DNSName
& qdomain
, const Netmask
& addr
, GeoIPNetmask
& gl
)
475 const auto& i
= dom
.records
.find(search
);
476 map
<uint16_t, int> cumul_probabilities
;
477 map
<uint16_t, bool> weighted_match
;
478 int probability_rnd
= 1 + (dns_random(1000)); // setting probability=0 means it never is used
480 if (i
!= dom
.records
.end()) { // return static value
481 for (const auto& rr
: i
->second
) {
482 if ((qtype
!= QType::ANY
&& rr
.qtype
!= qtype
) || weighted_match
[rr
.qtype
.getCode()])
486 gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
487 int comp
= cumul_probabilities
[rr
.qtype
.getCode()];
488 cumul_probabilities
[rr
.qtype
.getCode()] += rr
.weight
;
489 if (rr
.weight
== 0 || probability_rnd
< comp
|| probability_rnd
> (comp
+ rr
.weight
))
492 const string
& content
= format2str(rr
.content
, addr
, gl
, dom
);
493 if (rr
.qtype
!= QType::ENT
&& rr
.qtype
!= QType::TXT
&& content
.empty())
495 d_result
.push_back(rr
);
496 d_result
.back().content
= content
;
497 d_result
.back().qname
= qdomain
;
498 // If we are weighted we only return one resource and we found a matching resource,
499 // so no need to check the other ones.
501 weighted_match
[rr
.qtype
.getCode()] = true;
503 // ensure we get most strict netmask
504 for (DNSResourceRecord
& rr
: d_result
) {
505 rr
.scopeMask
= gl
.netmask
;
507 return true; // no need to go further
513 void GeoIPBackend::lookup(const QType
& qtype
, const DNSName
& qdomain
, domainid_t zoneId
, DNSPacket
* pkt_p
)
515 ReadLock
rl(&s_state_lock
);
516 const GeoIPDomain
* dom
;
520 if (d_result
.size() > 0)
521 throw PDNSException("Cannot perform lookup while another is running");
525 if (zoneId
>= 0 && zoneId
< static_cast<domainid_t
>(s_domains
.size())) {
526 dom
= &(s_domains
[zoneId
]);
529 for (const GeoIPDomain
& i
: s_domains
) { // this is arguably wrong, we should probably find the most specific match
530 if (qdomain
.isPartOf(i
.domain
)) {
540 Netmask addr
{"0.0.0.0/0"};
541 if (pkt_p
!= nullptr) {
542 addr
= Netmask(pkt_p
->getRealRemote());
547 (void)this->lookup_static(*dom
, qdomain
, qtype
, qdomain
, addr
, gl
);
549 const auto& target
= (*dom
).services
.find(qdomain
);
550 if (target
== (*dom
).services
.end())
553 const NetmaskTree
<vector
<string
>>::node_type
* node
= target
->second
.masks
.lookup(addr
);
555 return; // no hit, again.
558 gl
.netmask
= node
->first
.getBits();
559 // figure out smallest sensible netmask
560 if (gl
.netmask
== 0) {
563 // get netmask from geoip backend
564 if (queryGeoIP(addr
, GeoIPInterface::Name
, tmp_gl
) == "unknown") {
566 gl
.netmask
= target
->second
.netmask6
;
568 gl
.netmask
= target
->second
.netmask4
;
573 gl
.netmask
= target
->second
.netmask6
;
575 gl
.netmask
= target
->second
.netmask4
;
578 // note that this means the array format won't work with indirect
579 for (auto it
= node
->second
.begin(); it
!= node
->second
.end(); it
++) {
580 sformat
= DNSName(format2str(*it
, addr
, gl
, *dom
));
582 // see if the record can be found
583 if (this->lookup_static((*dom
), sformat
, qtype
, qdomain
, addr
, gl
))
587 if (!d_result
.empty()) {
588 g_log
<< Logger::Error
<< "Cannot have static record and CNAME at the same time."
589 << "Please fix your configuration for \"" << qdomain
<< "\", so that "
590 << "it can be resolved by GeoIP backend directly." << std::endl
;
595 // we need this line since we otherwise claim to have NS records etc
596 if (!(qtype
== QType::ANY
|| qtype
== QType::CNAME
))
599 DNSResourceRecord rr
;
600 rr
.domain_id
= dom
->id
;
601 rr
.qtype
= QType::CNAME
;
603 rr
.content
= sformat
.toString();
606 rr
.scopeMask
= gl
.netmask
;
607 d_result
.push_back(rr
);
610 bool GeoIPBackend::get(DNSResourceRecord
& r
)
612 if (d_result
.empty()) {
622 static string
queryGeoIP(const Netmask
& addr
, GeoIPInterface::GeoIPQueryAttribute attribute
, GeoIPNetmask
& gl
)
624 string ret
= "unknown";
626 for (auto const& gi
: s_geoip_files
) {
628 const string ip
= addr
.toStringNoMask();
632 case GeoIPInterface::ASn
:
634 found
= gi
->queryASnumV6(val
, gl
, ip
);
636 found
= gi
->queryASnum(val
, gl
, ip
);
638 case GeoIPInterface::Name
:
640 found
= gi
->queryNameV6(val
, gl
, ip
);
642 found
= gi
->queryName(val
, gl
, ip
);
644 case GeoIPInterface::Continent
:
646 found
= gi
->queryContinentV6(val
, gl
, ip
);
648 found
= gi
->queryContinent(val
, gl
, ip
);
650 case GeoIPInterface::Region
:
652 found
= gi
->queryRegionV6(val
, gl
, ip
);
654 found
= gi
->queryRegion(val
, gl
, ip
);
656 case GeoIPInterface::Country
:
658 found
= gi
->queryCountryV6(val
, gl
, ip
);
660 found
= gi
->queryCountry(val
, gl
, ip
);
662 case GeoIPInterface::Country2
:
664 found
= gi
->queryCountry2V6(val
, gl
, ip
);
666 found
= gi
->queryCountry2(val
, gl
, ip
);
668 case GeoIPInterface::City
:
670 found
= gi
->queryCityV6(val
, gl
, ip
);
672 found
= gi
->queryCity(val
, gl
, ip
);
674 case GeoIPInterface::Location
:
675 double lat
= 0, lon
= 0;
676 std::optional
<int> alt
;
677 std::optional
<int> prec
;
679 found
= gi
->queryLocationV6(gl
, ip
, lat
, lon
, alt
, prec
);
681 found
= gi
->queryLocation(gl
, ip
, lat
, lon
, alt
, prec
);
682 val
= std::to_string(lat
) + " " + std::to_string(lon
);
686 if (!found
|| val
.empty() || val
== "--") {
687 continue; // try next database
689 ret
= std::move(val
);
690 std::transform(ret
.begin(), ret
.end(), ret
.begin(), ::tolower
);
694 if (ret
== "unknown")
695 gl
.netmask
= (addr
.isIPv6() ? 128 : 32); // prevent caching
699 string
getGeoForLua(const std::string
& ip
, int qaint
)
701 GeoIPInterface::GeoIPQueryAttribute
qa((GeoIPInterface::GeoIPQueryAttribute
)qaint
);
703 const Netmask addr
{ip
};
705 string res
= queryGeoIP(addr
, qa
, gl
);
706 // cout<<"Result for "<<ip<<" lookup: "<<res<<endl;
707 if (qa
== GeoIPInterface::ASn
&& boost::starts_with(res
, "as"))
708 return res
.substr(2);
711 catch (std::exception
& e
) {
712 cout
<< "Error: " << e
.what() << endl
;
714 catch (PDNSException
& e
) {
715 cout
<< "Error: " << e
.reason
<< endl
;
720 static bool queryGeoLocation(const Netmask
& addr
, GeoIPNetmask
& gl
, double& lat
, double& lon
,
721 std::optional
<int>& alt
, std::optional
<int>& prec
)
723 for (auto const& gi
: s_geoip_files
) {
726 if (gi
->queryLocationV6(gl
, addr
.toStringNoMask(), lat
, lon
, alt
, prec
))
729 else if (gi
->queryLocation(gl
, addr
.toStringNoMask(), lat
, lon
, alt
, prec
))
735 string
GeoIPBackend::format2str(string sformat
, const Netmask
& addr
, GeoIPNetmask
& gl
, const GeoIPDomain
& dom
)
737 string::size_type cur
, last
;
738 std::optional
<int> alt
;
739 std::optional
<int> prec
;
741 time_t t
= time(nullptr);
742 GeoIPNetmask tmp_gl
; // largest wins
747 while ((cur
= sformat
.find('%', last
)) != string::npos
) {
751 if (!sformat
.compare(cur
, 3, "%mp")) {
753 for (const auto& lookupFormat
: dom
.mapping_lookup_formats
) {
754 auto it
= dom
.custom_mapping
.find(format2str(lookupFormat
, addr
, gl
, dom
));
755 if (it
!= dom
.custom_mapping
.end()) {
761 else if (!sformat
.compare(cur
, 3, "%cn")) {
762 rep
= queryGeoIP(addr
, GeoIPInterface::Continent
, tmp_gl
);
764 else if (!sformat
.compare(cur
, 3, "%co")) {
765 rep
= queryGeoIP(addr
, GeoIPInterface::Country
, tmp_gl
);
767 else if (!sformat
.compare(cur
, 3, "%cc")) {
768 rep
= queryGeoIP(addr
, GeoIPInterface::Country2
, tmp_gl
);
770 else if (!sformat
.compare(cur
, 3, "%af")) {
771 rep
= (addr
.isIPv6() ? "v6" : "v4");
773 else if (!sformat
.compare(cur
, 3, "%as")) {
774 rep
= queryGeoIP(addr
, GeoIPInterface::ASn
, tmp_gl
);
776 else if (!sformat
.compare(cur
, 3, "%re")) {
777 rep
= queryGeoIP(addr
, GeoIPInterface::Region
, tmp_gl
);
779 else if (!sformat
.compare(cur
, 3, "%na")) {
780 rep
= queryGeoIP(addr
, GeoIPInterface::Name
, tmp_gl
);
782 else if (!sformat
.compare(cur
, 3, "%ci")) {
783 rep
= queryGeoIP(addr
, GeoIPInterface::City
, tmp_gl
);
785 else if (!sformat
.compare(cur
, 4, "%loc")) {
789 if (!queryGeoLocation(addr
, gl
, lat
, lon
, alt
, prec
)) {
791 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
794 ns
= (lat
> 0) ? 'N' : 'S';
795 ew
= (lon
> 0) ? 'E' : 'W';
799 d1
= static_cast<int>(lat
);
800 d2
= static_cast<int>(lon
);
801 m1
= static_cast<int>((lat
- d1
) * 60.0);
802 m2
= static_cast<int>((lon
- d2
) * 60.0);
803 s1
= static_cast<double>(lat
- d1
- m1
/ 60.0) * 3600.0;
804 s2
= static_cast<double>(lon
- d2
- m2
/ 60.0) * 3600.0;
805 rep
= str(boost::format("%d %d %0.3f %c %d %d %0.3f %c") % d1
% m1
% s1
% ns
% d2
% m2
% s2
% ew
);
807 rep
= rep
+ str(boost::format(" %d.00") % *alt
);
809 rep
= rep
+ string(" 0.00");
811 rep
= rep
+ str(boost::format(" %dm") % *prec
);
815 else if (!sformat
.compare(cur
, 4, "%lat")) {
816 if (!queryGeoLocation(addr
, gl
, lat
, lon
, alt
, prec
)) {
818 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
821 rep
= str(boost::format("%lf") % lat
);
825 else if (!sformat
.compare(cur
, 4, "%lon")) {
826 if (!queryGeoLocation(addr
, gl
, lat
, lon
, alt
, prec
)) {
828 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
831 rep
= str(boost::format("%lf") % lon
);
835 else if (!sformat
.compare(cur
, 3, "%hh")) {
836 rep
= boost::str(boost::format("%02d") % gtm
.tm_hour
);
837 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
839 else if (!sformat
.compare(cur
, 3, "%yy")) {
840 rep
= boost::str(boost::format("%02d") % (gtm
.tm_year
+ 1900));
841 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
843 else if (!sformat
.compare(cur
, 3, "%dd")) {
844 rep
= boost::str(boost::format("%02d") % (gtm
.tm_yday
+ 1));
845 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
847 else if (!sformat
.compare(cur
, 4, "%wds")) {
849 rep
= GeoIP_WEEKDAYS
.at(gtm
.tm_wday
);
850 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
852 else if (!sformat
.compare(cur
, 4, "%mos")) {
854 rep
= GeoIP_MONTHS
.at(gtm
.tm_mon
);
855 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
857 else if (!sformat
.compare(cur
, 3, "%wd")) {
858 rep
= boost::str(boost::format("%02d") % (gtm
.tm_wday
+ 1));
859 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
861 else if (!sformat
.compare(cur
, 3, "%mo")) {
862 rep
= boost::str(boost::format("%02d") % (gtm
.tm_mon
+ 1));
863 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
865 else if (!sformat
.compare(cur
, 4, "%ip6")) {
868 rep
= addr
.toStringNoMask();
871 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
873 else if (!sformat
.compare(cur
, 4, "%ip4")) {
876 rep
= addr
.toStringNoMask();
879 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
881 else if (!sformat
.compare(cur
, 3, "%ip")) {
882 rep
= addr
.toStringNoMask();
883 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
885 else if (!sformat
.compare(cur
, 2, "%%")) {
893 if (tmp_gl
.netmask
> gl
.netmask
)
894 gl
.netmask
= tmp_gl
.netmask
;
895 sformat
.replace(cur
, nrep
, rep
);
896 last
= cur
+ rep
.size(); // move to next attribute
901 void GeoIPBackend::reload()
903 WriteLock
wl(&s_state_lock
);
908 catch (PDNSException
& pex
) {
909 g_log
<< Logger::Error
<< "GeoIP backend reload failed: " << pex
.reason
<< endl
;
911 catch (std::exception
& stex
) {
912 g_log
<< Logger::Error
<< "GeoIP backend reload failed: " << stex
.what() << endl
;
915 g_log
<< Logger::Error
<< "GeoIP backend reload failed" << endl
;
919 void GeoIPBackend::rediscover(string
* /* status */)
924 bool GeoIPBackend::getDomainInfo(const ZoneName
& domain
, DomainInfo
& info
, bool /* getSerial */)
926 ReadLock
rl(&s_state_lock
);
928 for (const GeoIPDomain
& dom
: s_domains
) {
929 if (dom
.domain
== domain
) {
931 this->getSOA(dom
.domain
, dom
.id
, sd
);
933 info
.zone
= dom
.domain
;
934 info
.serial
= sd
.serial
;
935 info
.kind
= DomainInfo::Native
;
943 void GeoIPBackend::getAllDomains(vector
<DomainInfo
>* domains
, bool /* getSerial */, bool /* include_disabled */)
945 ReadLock
rl(&s_state_lock
);
948 for (const auto& dom
: s_domains
) {
950 this->getSOA(dom
.domain
, dom
.id
, sd
);
952 di
.zone
= dom
.domain
;
953 di
.serial
= sd
.serial
;
954 di
.kind
= DomainInfo::Native
;
956 domains
->emplace_back(di
);
960 bool GeoIPBackend::getAllDomainMetadata(const ZoneName
& name
, std::map
<std::string
, std::vector
<std::string
>>& meta
)
965 ReadLock
rl(&s_state_lock
);
966 for (const GeoIPDomain
& dom
: s_domains
) {
967 if (dom
.domain
== name
) {
968 if (hasDNSSECkey(dom
.domain
)) {
969 meta
[string("NSEC3NARROW")].push_back("1");
970 meta
[string("NSEC3PARAM")].push_back("1 0 0 -");
978 bool GeoIPBackend::getDomainMetadata(const ZoneName
& name
, const std::string
& kind
, std::vector
<std::string
>& meta
)
983 ReadLock
rl(&s_state_lock
);
984 for (const GeoIPDomain
& dom
: s_domains
) {
985 if (dom
.domain
== name
) {
986 if (hasDNSSECkey(dom
.domain
)) {
987 if (kind
== "NSEC3NARROW")
988 meta
.push_back(string("1"));
989 if (kind
== "NSEC3PARAM")
990 meta
.push_back(string("1 0 1 f95a"));
998 bool GeoIPBackend::getDomainKeys(const ZoneName
& name
, std::vector
<DNSBackend::KeyData
>& keys
)
1002 ReadLock
rl(&s_state_lock
);
1003 for (const GeoIPDomain
& dom
: s_domains
) {
1004 if (dom
.domain
== name
) {
1007 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
| REG_EXTENDED
);
1008 ostringstream pathname
;
1009 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
1011 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
1012 for (size_t i
= 0; i
< glob_result
.gl_pathc
; i
++) {
1013 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
1014 DNSBackend::KeyData kd
;
1015 pdns::checked_stoi_into(kd
.id
, glob_result
.gl_pathv
[i
] + regm
[3].rm_so
);
1016 kd
.active
= !strncmp(glob_result
.gl_pathv
[i
] + regm
[4].rm_so
, "1", 1);
1017 kd
.published
= true;
1018 pdns::checked_stoi_into(kd
.flags
, glob_result
.gl_pathv
[i
] + regm
[2].rm_so
);
1019 ifstream
ifs(glob_result
.gl_pathv
[i
]);
1020 ostringstream content
;
1022 while (ifs
.good()) {
1023 ifs
.read(buffer
, sizeof buffer
);
1024 if (ifs
.gcount() > 0) {
1025 content
<< string(buffer
, ifs
.gcount());
1029 kd
.content
= content
.str();
1030 keys
.emplace_back(std::move(kd
));
1035 globfree(&glob_result
);
1042 bool GeoIPBackend::removeDomainKey(const ZoneName
& name
, unsigned int keyId
)
1046 WriteLock
rl(&s_state_lock
);
1049 for (const GeoIPDomain
& dom
: s_domains
) {
1050 if (dom
.domain
== name
) {
1053 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
| REG_EXTENDED
);
1054 ostringstream pathname
;
1055 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
1057 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
1058 for (size_t i
= 0; i
< glob_result
.gl_pathc
; i
++) {
1059 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
1060 auto kid
= pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[3].rm_so
);
1062 if (unlink(glob_result
.gl_pathv
[i
])) {
1063 cerr
<< "Cannot delete key:" << strerror(errno
) << endl
;
1071 globfree(&glob_result
);
1078 bool GeoIPBackend::addDomainKey(const ZoneName
& name
, const KeyData
& key
, int64_t& keyId
)
1082 WriteLock
rl(&s_state_lock
);
1083 unsigned int nextid
= 1;
1085 for (const GeoIPDomain
& dom
: s_domains
) {
1086 if (dom
.domain
== name
) {
1089 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
| REG_EXTENDED
);
1090 ostringstream pathname
;
1091 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
1093 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
1094 for (size_t i
= 0; i
< glob_result
.gl_pathc
; i
++) {
1095 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
1096 auto kid
= pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[3].rm_so
);
1103 globfree(&glob_result
);
1105 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "." << key
.flags
<< "." << nextid
<< "." << (key
.active
? "1" : "0") << ".key";
1106 ofstream
ofs(pathname
.str().c_str());
1107 ofs
.write(key
.content
.c_str(), key
.content
.size());
1116 bool GeoIPBackend::activateDomainKey(const ZoneName
& name
, unsigned int keyId
)
1120 WriteLock
rl(&s_state_lock
);
1121 for (const GeoIPDomain
& dom
: s_domains
) {
1122 if (dom
.domain
== name
) {
1125 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
| REG_EXTENDED
);
1126 ostringstream pathname
;
1127 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
1129 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
1130 for (size_t i
= 0; i
< glob_result
.gl_pathc
; i
++) {
1131 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
1132 auto kid
= pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[3].rm_so
);
1133 if (kid
== keyId
&& strcmp(glob_result
.gl_pathv
[i
] + regm
[4].rm_so
, "0") == 0) { // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1134 ostringstream newpath
;
1135 newpath
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "." << pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[2].rm_so
) << "." << kid
<< ".1.key";
1136 if (rename(glob_result
.gl_pathv
[i
], newpath
.str().c_str())) {
1137 cerr
<< "Cannot activate key: " << strerror(errno
) << endl
;
1143 globfree(&glob_result
);
1151 bool GeoIPBackend::deactivateDomainKey(const ZoneName
& name
, unsigned int keyId
)
1155 WriteLock
rl(&s_state_lock
);
1156 for (const GeoIPDomain
& dom
: s_domains
) {
1157 if (dom
.domain
== name
) {
1160 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
| REG_EXTENDED
);
1161 ostringstream pathname
;
1162 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
1164 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
1165 for (size_t i
= 0; i
< glob_result
.gl_pathc
; i
++) {
1166 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
1167 auto kid
= pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[3].rm_so
);
1168 if (kid
== keyId
&& strcmp(glob_result
.gl_pathv
[i
] + regm
[4].rm_so
, "1") == 0) { // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1169 ostringstream newpath
;
1170 newpath
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "." << pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[2].rm_so
) << "." << kid
<< ".0.key";
1171 if (rename(glob_result
.gl_pathv
[i
], newpath
.str().c_str())) {
1172 cerr
<< "Cannot deactivate key: " << strerror(errno
) << endl
;
1178 globfree(&glob_result
);
1186 bool GeoIPBackend::publishDomainKey(const ZoneName
& /* name */, unsigned int /* id */)
1191 bool GeoIPBackend::unpublishDomainKey(const ZoneName
& /* name */, unsigned int /* id */)
1196 bool GeoIPBackend::hasDNSSECkey(const ZoneName
& name
)
1198 ostringstream pathname
;
1199 pathname
<< getArg("dnssec-keydir") << "/" << name
.toStringNoDot() << "*.key";
1201 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
1202 globfree(&glob_result
);
1208 class GeoIPFactory
: public BackendFactory
1212 BackendFactory("geoip") {}
1214 void declareArguments(const string
& suffix
= "") override
1216 declare(suffix
, "zones-file", "YAML file to load zone(s) configuration", "");
1217 declare(suffix
, "database-files", "File(s) to load geoip data from ([driver:]path[;opt=value]", "");
1218 declare(suffix
, "dnssec-keydir", "Directory to hold dnssec keys (also turns DNSSEC on)", "");
1221 DNSBackend
* make(const string
& suffix
) override
1223 return new GeoIPBackend(suffix
);
1232 BackendMakers().report(std::make_unique
<GeoIPFactory
>());
1233 g_log
<< Logger::Info
<< "[geoipbackend] This is the geoip backend version " VERSION
1234 #ifndef REPRODUCIBLE
1235 << " (" __DATE__
" " __TIME__
")"
1237 << " reporting" << endl
;
1241 static GeoIPLoader geoiploader
;