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
83 GeoIPBackend::GeoIPBackend(const string
& suffix
)
85 WriteLock
writeLock(&s_state_lock
);
86 setArgPrefix("geoip" + suffix
);
87 if (!getArg("dnssec-keydir").empty()) {
88 auto dirHandle
= std::unique_ptr
<DIR, decltype(&closedir
)>(opendir(getArg("dnssec-keydir").c_str()), closedir
);
90 throw PDNSException("dnssec-keydir " + getArg("dnssec-keydir") + " does not exist");
94 if (s_rc
== 0) { // first instance gets to open everything
100 static vector
<std::unique_ptr
<GeoIPInterface
>> s_geoip_files
;
102 string
getGeoForLua(const std::string
& ip
, int qaint
);
103 static string
queryGeoIP(const Netmask
& addr
, GeoIPInterface::GeoIPQueryAttribute attribute
, GeoIPNetmask
& gl
);
105 // validateMappingLookupFormats validates any custom format provided by the
106 // user does not use the custom mapping placeholder again, else it would do an
107 // infinite recursion.
108 static bool validateMappingLookupFormats(const vector
<string
>& formats
)
110 string::size_type cur
= 0;
111 string::size_type last
= 0;
113 for (const auto& lookupFormat
: formats
) {
115 while ((cur
= lookupFormat
.find("%", last
)) != string::npos
) {
116 if (lookupFormat
.compare(cur
, 3, "%mp") == 0) {
120 if (lookupFormat
.compare(cur
, 2, "%%") == 0) { // Ensure escaped % is also accepted
124 last
= cur
+ 1; // move to next attribute
130 static vector
<GeoIPDNSResourceRecord
> makeDNSResourceRecord(GeoIPDomain
& dom
, DNSName name
)
132 GeoIPDNSResourceRecord resourceRecord
;
133 resourceRecord
.domain_id
= static_cast<int>(dom
.id
);
134 resourceRecord
.ttl
= dom
.ttl
;
135 resourceRecord
.qname
= std::move(name
);
136 resourceRecord
.qtype
= QType(0); // empty non terminal
137 resourceRecord
.content
= "";
138 resourceRecord
.auth
= true;
139 resourceRecord
.weight
= 100;
140 resourceRecord
.has_weight
= false;
141 vector
<GeoIPDNSResourceRecord
> rrs
;
142 rrs
.push_back(resourceRecord
);
146 void GeoIPBackend::setupNetmasks(const YAML::Node
& domain
, GeoIPDomain
& dom
)
148 for (auto service
= domain
["services"].begin(); service
!= domain
["services"].end(); service
++) {
149 unsigned int netmask4
= 0;
150 unsigned int netmask6
= 0;
151 DNSName serviceName
{service
->first
.as
<string
>()};
152 NetmaskTree
<vector
<string
>> netmaskTree
;
154 // if it's an another map, we need to iterate it again, otherwise we just add two root entries.
155 if (service
->second
.IsMap()) {
156 for (auto net
= service
->second
.begin(); net
!= service
->second
.end(); net
++) {
157 vector
<string
> value
;
158 if (net
->second
.IsSequence()) {
159 value
= net
->second
.as
<vector
<string
>>();
162 value
.push_back(net
->second
.as
<string
>());
164 if (net
->first
.as
<string
>() == "default") {
165 netmaskTree
.insert(Netmask("0.0.0.0/0")).second
.assign(value
.begin(), value
.end());
166 netmaskTree
.insert(Netmask("::/0")).second
.swap(value
);
169 Netmask netmask
{net
->first
.as
<string
>()};
170 netmaskTree
.insert(netmask
).second
.swap(value
);
171 if (netmask
.isIPv6() && netmask6
< netmask
.getBits()) {
172 netmask6
= netmask
.getBits();
174 if (!netmask
.isIPv6() && netmask4
< netmask
.getBits()) {
175 netmask4
= netmask
.getBits();
181 vector
<string
> value
;
182 if (service
->second
.IsSequence()) {
183 value
= service
->second
.as
<vector
<string
>>();
186 value
.push_back(service
->second
.as
<string
>());
188 netmaskTree
.insert(Netmask("0.0.0.0/0")).second
.assign(value
.begin(), value
.end());
189 netmaskTree
.insert(Netmask("::/0")).second
.swap(value
);
192 // Allow per domain override of mapping_lookup_formats and custom_mapping.
193 // If not defined, the global values will be used.
194 if (YAML::Node formats
= domain
["mapping_lookup_formats"]) {
195 auto mapping_lookup_formats
= formats
.as
<vector
<string
>>();
196 if (!validateMappingLookupFormats(mapping_lookup_formats
)) {
197 throw PDNSException(string("%mp is not allowed in mapping lookup formats of domain ") + dom
.domain
.toLogString());
200 dom
.mapping_lookup_formats
= mapping_lookup_formats
;
203 dom
.mapping_lookup_formats
= d_global_mapping_lookup_formats
;
205 if (YAML::Node mapping
= domain
["custom_mapping"]) {
206 dom
.custom_mapping
= mapping
.as
<map
<std::string
, std::string
>>();
209 dom
.custom_mapping
= d_global_custom_mapping
;
212 dom
.services
[serviceName
].netmask4
= netmask4
;
213 dom
.services
[serviceName
].netmask6
= netmask6
;
214 dom
.services
[serviceName
].masks
.swap(netmaskTree
);
218 bool GeoIPBackend::loadDomain(const YAML::Node
& domain
, std::uint32_t domainID
, GeoIPDomain
& dom
)
222 dom
.domain
= DNSName(domain
["domain"].as
<string
>());
223 dom
.ttl
= domain
["ttl"].as
<int>();
225 for (auto recs
= domain
["records"].begin(); recs
!= domain
["records"].end(); recs
++) {
226 DNSName qname
= DNSName(recs
->first
.as
<string
>());
227 vector
<GeoIPDNSResourceRecord
> rrs
;
229 for (auto item
= recs
->second
.begin(); item
!= recs
->second
.end(); item
++) {
230 auto rec
= item
->begin();
231 GeoIPDNSResourceRecord rr
;
232 rr
.domain_id
= static_cast<int>(dom
.id
);
235 if (rec
->first
.IsNull()) {
239 string qtype
= boost::to_upper_copy(rec
->first
.as
<string
>());
242 rr
.has_weight
= false;
244 if (rec
->second
.IsNull()) {
247 else if (rec
->second
.IsMap()) {
248 for (YAML::const_iterator iter
= rec
->second
.begin(); iter
!= rec
->second
.end(); iter
++) {
249 auto attr
= iter
->first
.as
<string
>();
250 if (attr
== "content") {
251 auto content
= iter
->second
.as
<string
>();
252 rr
.content
= std::move(content
);
254 else if (attr
== "weight") {
255 rr
.weight
= iter
->second
.as
<int>();
256 if (rr
.weight
<= 0) {
257 g_log
<< Logger::Error
<< "Weight must be positive for " << rr
.qname
<< endl
;
258 throw PDNSException(string("Weight must be positive for ") + rr
.qname
.toLogString());
260 rr
.has_weight
= true;
262 else if (attr
== "ttl") {
263 rr
.ttl
= iter
->second
.as
<int>();
266 g_log
<< Logger::Error
<< "Unsupported record attribute " << attr
<< " for " << rr
.qname
<< endl
;
267 throw PDNSException(string("Unsupported record attribute ") + attr
+ string(" for ") + rr
.qname
.toLogString());
272 auto content
= rec
->second
.as
<string
>();
273 rr
.content
= std::move(content
);
279 std::swap(dom
.records
[qname
], rrs
);
282 setupNetmasks(domain
, dom
);
284 // rectify the zone, first static records
285 for (auto& item
: dom
.records
) {
286 // ensure we have parent in records
287 DNSName name
= item
.first
;
288 while (name
.chopOff() && name
.isPartOf(dom
.domain
)) {
289 if (dom
.records
.find(name
) == dom
.records
.end() && (dom
.services
.count(name
) == 0U)) { // don't ENT out a service!
290 auto rrs
= makeDNSResourceRecord(dom
, name
);
291 std::swap(dom
.records
[name
], rrs
);
297 for (auto& item
: dom
.services
) {
298 // ensure we have parent in records
299 DNSName name
= item
.first
;
300 while (name
.chopOff() && name
.isPartOf(dom
.domain
)) {
301 if (dom
.records
.find(name
) == dom
.records
.end()) {
302 auto rrs
= makeDNSResourceRecord(dom
, name
);
303 std::swap(dom
.records
[name
], rrs
);
308 // finally fix weights
309 for (auto& item
: dom
.records
) {
310 map
<uint16_t, float> weights
;
311 map
<uint16_t, float> sums
;
312 map
<uint16_t, GeoIPDNSResourceRecord
*> lasts
;
313 bool has_weight
= false;
314 // first we look for used weight
315 for (const auto& resourceRecord
: item
.second
) {
316 weights
[resourceRecord
.qtype
.getCode()] += static_cast<float>(resourceRecord
.weight
);
317 if (resourceRecord
.has_weight
) {
322 // put them back as probabilities and values..
323 for (auto& resourceRecord
: item
.second
) {
324 uint16_t rr_type
= resourceRecord
.qtype
.getCode();
325 resourceRecord
.weight
= static_cast<int>((static_cast<float>(resourceRecord
.weight
) / weights
[rr_type
]) * 1000.0);
326 sums
[rr_type
] += static_cast<float>(resourceRecord
.weight
);
327 resourceRecord
.has_weight
= has_weight
;
328 lasts
[rr_type
] = &resourceRecord
;
330 // remove rounding gap
331 for (auto& x
: lasts
) {
332 float sum
= sums
[x
.first
];
334 x
.second
->weight
+= (1000 - sum
);
340 catch (std::exception
& ex
) {
341 g_log
<< Logger::Error
<< ex
.what() << endl
;
344 catch (PDNSException
& ex
) {
345 g_log
<< Logger::Error
<< ex
.reason
<< endl
;
351 void GeoIPBackend::loadDomainsFromDirectory(const std::string
& dir
, vector
<GeoIPDomain
>& domains
)
353 vector
<std::filesystem::path
> paths
;
354 for (const std::filesystem::path
& p
: std::filesystem::directory_iterator(std::filesystem::path(dir
))) {
355 if (std::filesystem::is_regular_file(p
) && p
.has_extension() && (p
.extension() == ".yaml" || p
.extension() == ".yml")) {
359 std::sort(paths
.begin(), paths
.end());
360 for (const auto& p
: paths
) {
363 const auto& zoneRoot
= YAML::LoadFile(p
.string());
365 const auto& zone
= zoneRoot
["zone"];
366 if (loadDomain(zone
, domains
.size(), dom
)) {
367 domains
.push_back(dom
);
370 catch (std::exception
& ex
) {
371 g_log
<< Logger::Warning
<< "Cannot load zone from " << p
<< ": " << ex
.what() << endl
;
376 void GeoIPBackend::initialize()
379 vector
<GeoIPDomain
> tmp_domains
;
381 s_geoip_files
.clear(); // reset pointers
383 if (getArg("database-files").empty() == false) {
384 vector
<string
> files
;
385 stringtok(files
, getArg("database-files"), " ,\t\r\n");
386 for (auto const& file
: files
) {
387 s_geoip_files
.push_back(GeoIPInterface::makeInterface(file
));
391 if (s_geoip_files
.empty()) {
392 g_log
<< Logger::Warning
<< "No GeoIP database files loaded!" << endl
;
395 if (!getArg("zones-file").empty()) {
397 config
= YAML::LoadFile(getArg("zones-file"));
399 catch (YAML::Exception
& ex
) {
400 throw PDNSException(string("Cannot read config file ") + ex
.msg
);
404 // Global lookup formats and mapping will be used
405 // if none defined at the domain level.
406 if (YAML::Node formats
= config
["mapping_lookup_formats"]) {
407 d_global_mapping_lookup_formats
= formats
.as
<vector
<string
>>();
408 if (!validateMappingLookupFormats(d_global_mapping_lookup_formats
)) {
409 throw PDNSException(string("%mp is not allowed in mapping lookup"));
412 if (YAML::Node mapping
= config
["custom_mapping"]) {
413 d_global_custom_mapping
= mapping
.as
<map
<std::string
, std::string
>>();
416 for (YAML::const_iterator _domain
= config
["domains"].begin(); _domain
!= config
["domains"].end(); _domain
++) {
418 if (loadDomain(*_domain
, tmp_domains
.size(), dom
)) {
419 tmp_domains
.push_back(std::move(dom
));
423 if (YAML::Node domain_dir
= config
["zones_dir"]) {
424 loadDomainsFromDirectory(domain_dir
.as
<string
>(), tmp_domains
);
428 std::swap(s_domains
, tmp_domains
);
430 extern std::function
<std::string(const std::string
& ip
, int)> g_getGeo
;
431 g_getGeo
= getGeoForLua
;
434 GeoIPBackend::~GeoIPBackend()
437 WriteLock
writeLock(&s_state_lock
);
439 if (s_rc
== 0) { // last instance gets to cleanup
440 s_geoip_files
.clear();
448 bool GeoIPBackend::lookup_static(const GeoIPDomain
& dom
, const DNSName
& search
, const QType
& qtype
, const DNSName
& qdomain
, const Netmask
& addr
, GeoIPNetmask
& gl
)
450 const auto& i
= dom
.records
.find(search
);
451 map
<uint16_t, int> cumul_probabilities
;
452 map
<uint16_t, bool> weighted_match
;
453 int probability_rnd
= 1 + (dns_random(1000)); // setting probability=0 means it never is used
455 if (i
!= dom
.records
.end()) { // return static value
456 for (const auto& rr
: i
->second
) {
457 if ((qtype
!= QType::ANY
&& rr
.qtype
!= qtype
) || weighted_match
[rr
.qtype
.getCode()])
461 gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
462 int comp
= cumul_probabilities
[rr
.qtype
.getCode()];
463 cumul_probabilities
[rr
.qtype
.getCode()] += rr
.weight
;
464 if (rr
.weight
== 0 || probability_rnd
< comp
|| probability_rnd
> (comp
+ rr
.weight
))
467 const string
& content
= format2str(rr
.content
, addr
, gl
, dom
);
468 if (rr
.qtype
!= QType::ENT
&& rr
.qtype
!= QType::TXT
&& content
.empty())
470 d_result
.push_back(rr
);
471 d_result
.back().content
= content
;
472 d_result
.back().qname
= qdomain
;
473 // If we are weighted we only return one resource and we found a matching resource,
474 // so no need to check the other ones.
476 weighted_match
[rr
.qtype
.getCode()] = true;
478 // ensure we get most strict netmask
479 for (DNSResourceRecord
& rr
: d_result
) {
480 rr
.scopeMask
= gl
.netmask
;
482 return true; // no need to go further
488 void GeoIPBackend::lookup(const QType
& qtype
, const DNSName
& qdomain
, int zoneId
, DNSPacket
* pkt_p
)
490 ReadLock
rl(&s_state_lock
);
491 const GeoIPDomain
* dom
;
495 if (d_result
.size() > 0)
496 throw PDNSException("Cannot perform lookup while another is running");
500 if (zoneId
> -1 && zoneId
< static_cast<int>(s_domains
.size()))
501 dom
= &(s_domains
[zoneId
]);
503 for (const GeoIPDomain
& i
: s_domains
) { // this is arguably wrong, we should probably find the most specific match
504 if (qdomain
.isPartOf(i
.domain
)) {
514 Netmask addr
{"0.0.0.0/0"};
515 if (pkt_p
!= nullptr)
516 addr
= Netmask(pkt_p
->getRealRemote());
520 (void)this->lookup_static(*dom
, qdomain
, qtype
, qdomain
, addr
, gl
);
522 const auto& target
= (*dom
).services
.find(qdomain
);
523 if (target
== (*dom
).services
.end())
526 const NetmaskTree
<vector
<string
>>::node_type
* node
= target
->second
.masks
.lookup(addr
);
528 return; // no hit, again.
531 gl
.netmask
= node
->first
.getBits();
532 // figure out smallest sensible netmask
533 if (gl
.netmask
== 0) {
536 // get netmask from geoip backend
537 if (queryGeoIP(addr
, GeoIPInterface::Name
, tmp_gl
) == "unknown") {
539 gl
.netmask
= target
->second
.netmask6
;
541 gl
.netmask
= target
->second
.netmask4
;
546 gl
.netmask
= target
->second
.netmask6
;
548 gl
.netmask
= target
->second
.netmask4
;
551 // note that this means the array format won't work with indirect
552 for (auto it
= node
->second
.begin(); it
!= node
->second
.end(); it
++) {
553 sformat
= DNSName(format2str(*it
, addr
, gl
, *dom
));
555 // see if the record can be found
556 if (this->lookup_static((*dom
), sformat
, qtype
, qdomain
, addr
, gl
))
560 if (!d_result
.empty()) {
561 g_log
<< Logger::Error
<< "Cannot have static record and CNAME at the same time."
562 << "Please fix your configuration for \"" << qdomain
<< "\", so that "
563 << "it can be resolved by GeoIP backend directly." << std::endl
;
568 // we need this line since we otherwise claim to have NS records etc
569 if (!(qtype
== QType::ANY
|| qtype
== QType::CNAME
))
572 DNSResourceRecord rr
;
573 rr
.domain_id
= dom
->id
;
574 rr
.qtype
= QType::CNAME
;
576 rr
.content
= sformat
.toString();
579 rr
.scopeMask
= gl
.netmask
;
580 d_result
.push_back(rr
);
583 bool GeoIPBackend::get(DNSResourceRecord
& r
)
585 if (d_result
.empty())
594 static string
queryGeoIP(const Netmask
& addr
, GeoIPInterface::GeoIPQueryAttribute attribute
, GeoIPNetmask
& gl
)
596 string ret
= "unknown";
598 for (auto const& gi
: s_geoip_files
) {
600 const string ip
= addr
.toStringNoMask();
604 case GeoIPInterface::ASn
:
606 found
= gi
->queryASnumV6(val
, gl
, ip
);
608 found
= gi
->queryASnum(val
, gl
, ip
);
610 case GeoIPInterface::Name
:
612 found
= gi
->queryNameV6(val
, gl
, ip
);
614 found
= gi
->queryName(val
, gl
, ip
);
616 case GeoIPInterface::Continent
:
618 found
= gi
->queryContinentV6(val
, gl
, ip
);
620 found
= gi
->queryContinent(val
, gl
, ip
);
622 case GeoIPInterface::Region
:
624 found
= gi
->queryRegionV6(val
, gl
, ip
);
626 found
= gi
->queryRegion(val
, gl
, ip
);
628 case GeoIPInterface::Country
:
630 found
= gi
->queryCountryV6(val
, gl
, ip
);
632 found
= gi
->queryCountry(val
, gl
, ip
);
634 case GeoIPInterface::Country2
:
636 found
= gi
->queryCountry2V6(val
, gl
, ip
);
638 found
= gi
->queryCountry2(val
, gl
, ip
);
640 case GeoIPInterface::City
:
642 found
= gi
->queryCityV6(val
, gl
, ip
);
644 found
= gi
->queryCity(val
, gl
, ip
);
646 case GeoIPInterface::Location
:
647 double lat
= 0, lon
= 0;
648 boost::optional
<int> alt
, prec
;
650 found
= gi
->queryLocationV6(gl
, ip
, lat
, lon
, alt
, prec
);
652 found
= gi
->queryLocation(gl
, ip
, lat
, lon
, alt
, prec
);
653 val
= std::to_string(lat
) + " " + std::to_string(lon
);
657 if (!found
|| val
.empty() || val
== "--")
658 continue; // try next database
660 std::transform(ret
.begin(), ret
.end(), ret
.begin(), ::tolower
);
664 if (ret
== "unknown")
665 gl
.netmask
= (addr
.isIPv6() ? 128 : 32); // prevent caching
669 string
getGeoForLua(const std::string
& ip
, int qaint
)
671 GeoIPInterface::GeoIPQueryAttribute
qa((GeoIPInterface::GeoIPQueryAttribute
)qaint
);
673 const Netmask addr
{ip
};
675 string res
= queryGeoIP(addr
, qa
, gl
);
676 // cout<<"Result for "<<ip<<" lookup: "<<res<<endl;
677 if (qa
== GeoIPInterface::ASn
&& boost::starts_with(res
, "as"))
678 return res
.substr(2);
681 catch (std::exception
& e
) {
682 cout
<< "Error: " << e
.what() << endl
;
684 catch (PDNSException
& e
) {
685 cout
<< "Error: " << e
.reason
<< endl
;
690 static bool queryGeoLocation(const Netmask
& addr
, GeoIPNetmask
& gl
, double& lat
, double& lon
,
691 boost::optional
<int>& alt
, boost::optional
<int>& prec
)
693 for (auto const& gi
: s_geoip_files
) {
696 if (gi
->queryLocationV6(gl
, addr
.toStringNoMask(), lat
, lon
, alt
, prec
))
699 else if (gi
->queryLocation(gl
, addr
.toStringNoMask(), lat
, lon
, alt
, prec
))
705 string
GeoIPBackend::format2str(string sformat
, const Netmask
& addr
, GeoIPNetmask
& gl
, const GeoIPDomain
& dom
)
707 string::size_type cur
, last
;
708 boost::optional
<int> alt
, prec
;
710 time_t t
= time(nullptr);
711 GeoIPNetmask tmp_gl
; // largest wins
716 while ((cur
= sformat
.find('%', last
)) != string::npos
) {
720 if (!sformat
.compare(cur
, 3, "%mp")) {
722 for (const auto& lookupFormat
: dom
.mapping_lookup_formats
) {
723 auto it
= dom
.custom_mapping
.find(format2str(lookupFormat
, addr
, gl
, dom
));
724 if (it
!= dom
.custom_mapping
.end()) {
730 else if (!sformat
.compare(cur
, 3, "%cn")) {
731 rep
= queryGeoIP(addr
, GeoIPInterface::Continent
, tmp_gl
);
733 else if (!sformat
.compare(cur
, 3, "%co")) {
734 rep
= queryGeoIP(addr
, GeoIPInterface::Country
, tmp_gl
);
736 else if (!sformat
.compare(cur
, 3, "%cc")) {
737 rep
= queryGeoIP(addr
, GeoIPInterface::Country2
, tmp_gl
);
739 else if (!sformat
.compare(cur
, 3, "%af")) {
740 rep
= (addr
.isIPv6() ? "v6" : "v4");
742 else if (!sformat
.compare(cur
, 3, "%as")) {
743 rep
= queryGeoIP(addr
, GeoIPInterface::ASn
, tmp_gl
);
745 else if (!sformat
.compare(cur
, 3, "%re")) {
746 rep
= queryGeoIP(addr
, GeoIPInterface::Region
, tmp_gl
);
748 else if (!sformat
.compare(cur
, 3, "%na")) {
749 rep
= queryGeoIP(addr
, GeoIPInterface::Name
, tmp_gl
);
751 else if (!sformat
.compare(cur
, 3, "%ci")) {
752 rep
= queryGeoIP(addr
, GeoIPInterface::City
, tmp_gl
);
754 else if (!sformat
.compare(cur
, 4, "%loc")) {
758 if (!queryGeoLocation(addr
, gl
, lat
, lon
, alt
, prec
)) {
760 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
763 ns
= (lat
> 0) ? 'N' : 'S';
764 ew
= (lon
> 0) ? 'E' : 'W';
768 d1
= static_cast<int>(lat
);
769 d2
= static_cast<int>(lon
);
770 m1
= static_cast<int>((lat
- d1
) * 60.0);
771 m2
= static_cast<int>((lon
- d2
) * 60.0);
772 s1
= static_cast<double>(lat
- d1
- m1
/ 60.0) * 3600.0;
773 s2
= static_cast<double>(lon
- d2
- m2
/ 60.0) * 3600.0;
774 rep
= str(boost::format("%d %d %0.3f %c %d %d %0.3f %c") % d1
% m1
% s1
% ns
% d2
% m2
% s2
% ew
);
776 rep
= rep
+ str(boost::format(" %d.00") % *alt
);
778 rep
= rep
+ string(" 0.00");
780 rep
= rep
+ str(boost::format(" %dm") % *prec
);
784 else if (!sformat
.compare(cur
, 4, "%lat")) {
785 if (!queryGeoLocation(addr
, gl
, lat
, lon
, alt
, prec
)) {
787 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
790 rep
= str(boost::format("%lf") % lat
);
794 else if (!sformat
.compare(cur
, 4, "%lon")) {
795 if (!queryGeoLocation(addr
, gl
, lat
, lon
, alt
, prec
)) {
797 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
800 rep
= str(boost::format("%lf") % lon
);
804 else if (!sformat
.compare(cur
, 3, "%hh")) {
805 rep
= boost::str(boost::format("%02d") % gtm
.tm_hour
);
806 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
808 else if (!sformat
.compare(cur
, 3, "%yy")) {
809 rep
= boost::str(boost::format("%02d") % (gtm
.tm_year
+ 1900));
810 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
812 else if (!sformat
.compare(cur
, 3, "%dd")) {
813 rep
= boost::str(boost::format("%02d") % (gtm
.tm_yday
+ 1));
814 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
816 else if (!sformat
.compare(cur
, 4, "%wds")) {
818 rep
= GeoIP_WEEKDAYS
.at(gtm
.tm_wday
);
819 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
821 else if (!sformat
.compare(cur
, 4, "%mos")) {
823 rep
= GeoIP_MONTHS
.at(gtm
.tm_mon
);
824 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
826 else if (!sformat
.compare(cur
, 3, "%wd")) {
827 rep
= boost::str(boost::format("%02d") % (gtm
.tm_wday
+ 1));
828 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
830 else if (!sformat
.compare(cur
, 3, "%mo")) {
831 rep
= boost::str(boost::format("%02d") % (gtm
.tm_mon
+ 1));
832 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
834 else if (!sformat
.compare(cur
, 4, "%ip6")) {
837 rep
= addr
.toStringNoMask();
840 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
842 else if (!sformat
.compare(cur
, 4, "%ip4")) {
845 rep
= addr
.toStringNoMask();
848 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
850 else if (!sformat
.compare(cur
, 3, "%ip")) {
851 rep
= addr
.toStringNoMask();
852 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
854 else if (!sformat
.compare(cur
, 2, "%%")) {
862 if (tmp_gl
.netmask
> gl
.netmask
)
863 gl
.netmask
= tmp_gl
.netmask
;
864 sformat
.replace(cur
, nrep
, rep
);
865 last
= cur
+ rep
.size(); // move to next attribute
870 void GeoIPBackend::reload()
872 WriteLock
wl(&s_state_lock
);
877 catch (PDNSException
& pex
) {
878 g_log
<< Logger::Error
<< "GeoIP backend reload failed: " << pex
.reason
<< endl
;
880 catch (std::exception
& stex
) {
881 g_log
<< Logger::Error
<< "GeoIP backend reload failed: " << stex
.what() << endl
;
884 g_log
<< Logger::Error
<< "GeoIP backend reload failed" << endl
;
888 void GeoIPBackend::rediscover(string
* /* status */)
893 bool GeoIPBackend::getDomainInfo(const DNSName
& domain
, DomainInfo
& di
, bool /* getSerial */)
895 ReadLock
rl(&s_state_lock
);
897 for (GeoIPDomain dom
: s_domains
) {
898 if (dom
.domain
== domain
) {
900 this->getSOA(domain
, sd
);
902 di
.zone
= dom
.domain
;
903 di
.serial
= sd
.serial
;
904 di
.kind
= DomainInfo::Native
;
912 void GeoIPBackend::getAllDomains(vector
<DomainInfo
>* domains
, bool /* getSerial */, bool /* include_disabled */)
914 ReadLock
rl(&s_state_lock
);
917 for (const auto& dom
: s_domains
) {
919 this->getSOA(dom
.domain
, sd
);
921 di
.zone
= dom
.domain
;
922 di
.serial
= sd
.serial
;
923 di
.kind
= DomainInfo::Native
;
925 domains
->emplace_back(di
);
929 bool GeoIPBackend::getAllDomainMetadata(const DNSName
& name
, std::map
<std::string
, std::vector
<std::string
>>& meta
)
934 ReadLock
rl(&s_state_lock
);
935 for (GeoIPDomain dom
: s_domains
) {
936 if (dom
.domain
== name
) {
937 if (hasDNSSECkey(dom
.domain
)) {
938 meta
[string("NSEC3NARROW")].push_back("1");
939 meta
[string("NSEC3PARAM")].push_back("1 0 1 f95a");
947 bool GeoIPBackend::getDomainMetadata(const DNSName
& name
, const std::string
& kind
, std::vector
<std::string
>& meta
)
952 ReadLock
rl(&s_state_lock
);
953 for (GeoIPDomain dom
: s_domains
) {
954 if (dom
.domain
== name
) {
955 if (hasDNSSECkey(dom
.domain
)) {
956 if (kind
== "NSEC3NARROW")
957 meta
.push_back(string("1"));
958 if (kind
== "NSEC3PARAM")
959 meta
.push_back(string("1 0 1 f95a"));
967 bool GeoIPBackend::getDomainKeys(const DNSName
& name
, std::vector
<DNSBackend::KeyData
>& keys
)
971 ReadLock
rl(&s_state_lock
);
972 for (GeoIPDomain dom
: s_domains
) {
973 if (dom
.domain
== name
) {
976 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
| REG_EXTENDED
);
977 ostringstream pathname
;
978 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
980 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
981 for (size_t i
= 0; i
< glob_result
.gl_pathc
; i
++) {
982 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
983 DNSBackend::KeyData kd
;
984 pdns::checked_stoi_into(kd
.id
, glob_result
.gl_pathv
[i
] + regm
[3].rm_so
);
985 kd
.active
= !strncmp(glob_result
.gl_pathv
[i
] + regm
[4].rm_so
, "1", 1);
987 pdns::checked_stoi_into(kd
.flags
, glob_result
.gl_pathv
[i
] + regm
[2].rm_so
);
988 ifstream
ifs(glob_result
.gl_pathv
[i
]);
989 ostringstream content
;
992 ifs
.read(buffer
, sizeof buffer
);
993 if (ifs
.gcount() > 0) {
994 content
<< string(buffer
, ifs
.gcount());
998 kd
.content
= content
.str();
1004 globfree(&glob_result
);
1011 bool GeoIPBackend::removeDomainKey(const DNSName
& name
, unsigned int id
)
1015 WriteLock
rl(&s_state_lock
);
1018 for (GeoIPDomain dom
: s_domains
) {
1019 if (dom
.domain
== name
) {
1022 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
| REG_EXTENDED
);
1023 ostringstream pathname
;
1024 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
1026 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
1027 for (size_t i
= 0; i
< glob_result
.gl_pathc
; i
++) {
1028 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
1029 auto kid
= pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[3].rm_so
);
1031 if (unlink(glob_result
.gl_pathv
[i
])) {
1032 cerr
<< "Cannot delete key:" << strerror(errno
) << endl
;
1040 globfree(&glob_result
);
1047 bool GeoIPBackend::addDomainKey(const DNSName
& name
, const KeyData
& key
, int64_t& id
)
1051 WriteLock
rl(&s_state_lock
);
1052 unsigned int nextid
= 1;
1054 for (GeoIPDomain dom
: s_domains
) {
1055 if (dom
.domain
== name
) {
1058 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
| REG_EXTENDED
);
1059 ostringstream pathname
;
1060 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
1062 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
1063 for (size_t i
= 0; i
< glob_result
.gl_pathc
; i
++) {
1064 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
1065 auto kid
= pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[3].rm_so
);
1072 globfree(&glob_result
);
1074 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "." << key
.flags
<< "." << nextid
<< "." << (key
.active
? "1" : "0") << ".key";
1075 ofstream
ofs(pathname
.str().c_str());
1076 ofs
.write(key
.content
.c_str(), key
.content
.size());
1085 bool GeoIPBackend::activateDomainKey(const DNSName
& name
, unsigned int id
)
1089 WriteLock
rl(&s_state_lock
);
1090 for (GeoIPDomain dom
: s_domains
) {
1091 if (dom
.domain
== name
) {
1094 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
| REG_EXTENDED
);
1095 ostringstream pathname
;
1096 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
1098 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
1099 for (size_t i
= 0; i
< glob_result
.gl_pathc
; i
++) {
1100 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
1101 auto kid
= pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[3].rm_so
);
1102 if (kid
== id
&& !strcmp(glob_result
.gl_pathv
[i
] + regm
[4].rm_so
, "0")) {
1103 ostringstream newpath
;
1104 newpath
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "." << pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[2].rm_so
) << "." << kid
<< ".1.key";
1105 if (rename(glob_result
.gl_pathv
[i
], newpath
.str().c_str())) {
1106 cerr
<< "Cannot activate key: " << strerror(errno
) << endl
;
1112 globfree(&glob_result
);
1120 bool GeoIPBackend::deactivateDomainKey(const DNSName
& name
, unsigned int id
)
1124 WriteLock
rl(&s_state_lock
);
1125 for (GeoIPDomain dom
: s_domains
) {
1126 if (dom
.domain
== name
) {
1129 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
| REG_EXTENDED
);
1130 ostringstream pathname
;
1131 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
1133 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
1134 for (size_t i
= 0; i
< glob_result
.gl_pathc
; i
++) {
1135 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
1136 auto kid
= pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[3].rm_so
);
1137 if (kid
== id
&& !strcmp(glob_result
.gl_pathv
[i
] + regm
[4].rm_so
, "1")) {
1138 ostringstream newpath
;
1139 newpath
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "." << pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[2].rm_so
) << "." << kid
<< ".0.key";
1140 if (rename(glob_result
.gl_pathv
[i
], newpath
.str().c_str())) {
1141 cerr
<< "Cannot deactivate key: " << strerror(errno
) << endl
;
1147 globfree(&glob_result
);
1155 bool GeoIPBackend::publishDomainKey(const DNSName
& /* name */, unsigned int /* id */)
1160 bool GeoIPBackend::unpublishDomainKey(const DNSName
& /* name */, unsigned int /* id */)
1165 bool GeoIPBackend::hasDNSSECkey(const DNSName
& name
)
1167 ostringstream pathname
;
1168 pathname
<< getArg("dnssec-keydir") << "/" << name
.toStringNoDot() << "*.key";
1170 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
1171 globfree(&glob_result
);
1177 class GeoIPFactory
: public BackendFactory
1181 BackendFactory("geoip") {}
1183 void declareArguments(const string
& suffix
= "") override
1185 declare(suffix
, "zones-file", "YAML file to load zone(s) configuration", "");
1186 declare(suffix
, "database-files", "File(s) to load geoip data from ([driver:]path[;opt=value]", "");
1187 declare(suffix
, "dnssec-keydir", "Directory to hold dnssec keys (also turns DNSSEC on)", "");
1190 DNSBackend
* make(const string
& suffix
) override
1192 return new GeoIPBackend(suffix
);
1201 BackendMakers().report(new GeoIPFactory
);
1202 g_log
<< Logger::Info
<< "[geoipbackend] This is the geoip backend version " VERSION
1203 #ifndef REPRODUCIBLE
1204 << " (" __DATE__
" " __TIME__
")"
1206 << " reporting" << endl
;
1210 static GeoIPLoader geoiploader
;