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
= std::move(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 ret
= std::move(val
);
661 std::transform(ret
.begin(), ret
.end(), ret
.begin(), ::tolower
);
665 if (ret
== "unknown")
666 gl
.netmask
= (addr
.isIPv6() ? 128 : 32); // prevent caching
670 string
getGeoForLua(const std::string
& ip
, int qaint
)
672 GeoIPInterface::GeoIPQueryAttribute
qa((GeoIPInterface::GeoIPQueryAttribute
)qaint
);
674 const Netmask addr
{ip
};
676 string res
= queryGeoIP(addr
, qa
, gl
);
677 // cout<<"Result for "<<ip<<" lookup: "<<res<<endl;
678 if (qa
== GeoIPInterface::ASn
&& boost::starts_with(res
, "as"))
679 return res
.substr(2);
682 catch (std::exception
& e
) {
683 cout
<< "Error: " << e
.what() << endl
;
685 catch (PDNSException
& e
) {
686 cout
<< "Error: " << e
.reason
<< endl
;
691 static bool queryGeoLocation(const Netmask
& addr
, GeoIPNetmask
& gl
, double& lat
, double& lon
,
692 boost::optional
<int>& alt
, boost::optional
<int>& prec
)
694 for (auto const& gi
: s_geoip_files
) {
697 if (gi
->queryLocationV6(gl
, addr
.toStringNoMask(), lat
, lon
, alt
, prec
))
700 else if (gi
->queryLocation(gl
, addr
.toStringNoMask(), lat
, lon
, alt
, prec
))
706 string
GeoIPBackend::format2str(string sformat
, const Netmask
& addr
, GeoIPNetmask
& gl
, const GeoIPDomain
& dom
)
708 string::size_type cur
, last
;
709 boost::optional
<int> alt
, prec
;
711 time_t t
= time(nullptr);
712 GeoIPNetmask tmp_gl
; // largest wins
717 while ((cur
= sformat
.find('%', last
)) != string::npos
) {
721 if (!sformat
.compare(cur
, 3, "%mp")) {
723 for (const auto& lookupFormat
: dom
.mapping_lookup_formats
) {
724 auto it
= dom
.custom_mapping
.find(format2str(lookupFormat
, addr
, gl
, dom
));
725 if (it
!= dom
.custom_mapping
.end()) {
731 else if (!sformat
.compare(cur
, 3, "%cn")) {
732 rep
= queryGeoIP(addr
, GeoIPInterface::Continent
, tmp_gl
);
734 else if (!sformat
.compare(cur
, 3, "%co")) {
735 rep
= queryGeoIP(addr
, GeoIPInterface::Country
, tmp_gl
);
737 else if (!sformat
.compare(cur
, 3, "%cc")) {
738 rep
= queryGeoIP(addr
, GeoIPInterface::Country2
, tmp_gl
);
740 else if (!sformat
.compare(cur
, 3, "%af")) {
741 rep
= (addr
.isIPv6() ? "v6" : "v4");
743 else if (!sformat
.compare(cur
, 3, "%as")) {
744 rep
= queryGeoIP(addr
, GeoIPInterface::ASn
, tmp_gl
);
746 else if (!sformat
.compare(cur
, 3, "%re")) {
747 rep
= queryGeoIP(addr
, GeoIPInterface::Region
, tmp_gl
);
749 else if (!sformat
.compare(cur
, 3, "%na")) {
750 rep
= queryGeoIP(addr
, GeoIPInterface::Name
, tmp_gl
);
752 else if (!sformat
.compare(cur
, 3, "%ci")) {
753 rep
= queryGeoIP(addr
, GeoIPInterface::City
, tmp_gl
);
755 else if (!sformat
.compare(cur
, 4, "%loc")) {
759 if (!queryGeoLocation(addr
, gl
, lat
, lon
, alt
, prec
)) {
761 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
764 ns
= (lat
> 0) ? 'N' : 'S';
765 ew
= (lon
> 0) ? 'E' : 'W';
769 d1
= static_cast<int>(lat
);
770 d2
= static_cast<int>(lon
);
771 m1
= static_cast<int>((lat
- d1
) * 60.0);
772 m2
= static_cast<int>((lon
- d2
) * 60.0);
773 s1
= static_cast<double>(lat
- d1
- m1
/ 60.0) * 3600.0;
774 s2
= static_cast<double>(lon
- d2
- m2
/ 60.0) * 3600.0;
775 rep
= str(boost::format("%d %d %0.3f %c %d %d %0.3f %c") % d1
% m1
% s1
% ns
% d2
% m2
% s2
% ew
);
777 rep
= rep
+ str(boost::format(" %d.00") % *alt
);
779 rep
= rep
+ string(" 0.00");
781 rep
= rep
+ str(boost::format(" %dm") % *prec
);
785 else if (!sformat
.compare(cur
, 4, "%lat")) {
786 if (!queryGeoLocation(addr
, gl
, lat
, lon
, alt
, prec
)) {
788 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
791 rep
= str(boost::format("%lf") % lat
);
795 else if (!sformat
.compare(cur
, 4, "%lon")) {
796 if (!queryGeoLocation(addr
, gl
, lat
, lon
, alt
, prec
)) {
798 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
801 rep
= str(boost::format("%lf") % lon
);
805 else if (!sformat
.compare(cur
, 3, "%hh")) {
806 rep
= boost::str(boost::format("%02d") % gtm
.tm_hour
);
807 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
809 else if (!sformat
.compare(cur
, 3, "%yy")) {
810 rep
= boost::str(boost::format("%02d") % (gtm
.tm_year
+ 1900));
811 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
813 else if (!sformat
.compare(cur
, 3, "%dd")) {
814 rep
= boost::str(boost::format("%02d") % (gtm
.tm_yday
+ 1));
815 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
817 else if (!sformat
.compare(cur
, 4, "%wds")) {
819 rep
= GeoIP_WEEKDAYS
.at(gtm
.tm_wday
);
820 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
822 else if (!sformat
.compare(cur
, 4, "%mos")) {
824 rep
= GeoIP_MONTHS
.at(gtm
.tm_mon
);
825 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
827 else if (!sformat
.compare(cur
, 3, "%wd")) {
828 rep
= boost::str(boost::format("%02d") % (gtm
.tm_wday
+ 1));
829 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
831 else if (!sformat
.compare(cur
, 3, "%mo")) {
832 rep
= boost::str(boost::format("%02d") % (gtm
.tm_mon
+ 1));
833 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
835 else if (!sformat
.compare(cur
, 4, "%ip6")) {
838 rep
= addr
.toStringNoMask();
841 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
843 else if (!sformat
.compare(cur
, 4, "%ip4")) {
846 rep
= addr
.toStringNoMask();
849 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
851 else if (!sformat
.compare(cur
, 3, "%ip")) {
852 rep
= addr
.toStringNoMask();
853 tmp_gl
.netmask
= (addr
.isIPv6() ? 128 : 32);
855 else if (!sformat
.compare(cur
, 2, "%%")) {
863 if (tmp_gl
.netmask
> gl
.netmask
)
864 gl
.netmask
= tmp_gl
.netmask
;
865 sformat
.replace(cur
, nrep
, rep
);
866 last
= cur
+ rep
.size(); // move to next attribute
871 void GeoIPBackend::reload()
873 WriteLock
wl(&s_state_lock
);
878 catch (PDNSException
& pex
) {
879 g_log
<< Logger::Error
<< "GeoIP backend reload failed: " << pex
.reason
<< endl
;
881 catch (std::exception
& stex
) {
882 g_log
<< Logger::Error
<< "GeoIP backend reload failed: " << stex
.what() << endl
;
885 g_log
<< Logger::Error
<< "GeoIP backend reload failed" << endl
;
889 void GeoIPBackend::rediscover(string
* /* status */)
894 bool GeoIPBackend::getDomainInfo(const DNSName
& domain
, DomainInfo
& di
, bool /* getSerial */)
896 ReadLock
rl(&s_state_lock
);
898 for (GeoIPDomain dom
: s_domains
) {
899 if (dom
.domain
== domain
) {
901 this->getSOA(domain
, sd
);
903 di
.zone
= dom
.domain
;
904 di
.serial
= sd
.serial
;
905 di
.kind
= DomainInfo::Native
;
913 void GeoIPBackend::getAllDomains(vector
<DomainInfo
>* domains
, bool /* getSerial */, bool /* include_disabled */)
915 ReadLock
rl(&s_state_lock
);
918 for (const auto& dom
: s_domains
) {
920 this->getSOA(dom
.domain
, sd
);
922 di
.zone
= dom
.domain
;
923 di
.serial
= sd
.serial
;
924 di
.kind
= DomainInfo::Native
;
926 domains
->emplace_back(di
);
930 bool GeoIPBackend::getAllDomainMetadata(const DNSName
& name
, std::map
<std::string
, std::vector
<std::string
>>& meta
)
935 ReadLock
rl(&s_state_lock
);
936 for (GeoIPDomain dom
: s_domains
) {
937 if (dom
.domain
== name
) {
938 if (hasDNSSECkey(dom
.domain
)) {
939 meta
[string("NSEC3NARROW")].push_back("1");
940 meta
[string("NSEC3PARAM")].push_back("1 0 1 f95a");
948 bool GeoIPBackend::getDomainMetadata(const DNSName
& name
, const std::string
& kind
, std::vector
<std::string
>& meta
)
953 ReadLock
rl(&s_state_lock
);
954 for (GeoIPDomain dom
: s_domains
) {
955 if (dom
.domain
== name
) {
956 if (hasDNSSECkey(dom
.domain
)) {
957 if (kind
== "NSEC3NARROW")
958 meta
.push_back(string("1"));
959 if (kind
== "NSEC3PARAM")
960 meta
.push_back(string("1 0 1 f95a"));
968 bool GeoIPBackend::getDomainKeys(const DNSName
& name
, std::vector
<DNSBackend::KeyData
>& keys
)
972 ReadLock
rl(&s_state_lock
);
973 for (GeoIPDomain dom
: s_domains
) {
974 if (dom
.domain
== name
) {
977 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
| REG_EXTENDED
);
978 ostringstream pathname
;
979 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
981 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
982 for (size_t i
= 0; i
< glob_result
.gl_pathc
; i
++) {
983 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
984 DNSBackend::KeyData kd
;
985 pdns::checked_stoi_into(kd
.id
, glob_result
.gl_pathv
[i
] + regm
[3].rm_so
);
986 kd
.active
= !strncmp(glob_result
.gl_pathv
[i
] + regm
[4].rm_so
, "1", 1);
988 pdns::checked_stoi_into(kd
.flags
, glob_result
.gl_pathv
[i
] + regm
[2].rm_so
);
989 ifstream
ifs(glob_result
.gl_pathv
[i
]);
990 ostringstream content
;
993 ifs
.read(buffer
, sizeof buffer
);
994 if (ifs
.gcount() > 0) {
995 content
<< string(buffer
, ifs
.gcount());
999 kd
.content
= content
.str();
1005 globfree(&glob_result
);
1012 bool GeoIPBackend::removeDomainKey(const DNSName
& name
, unsigned int id
)
1016 WriteLock
rl(&s_state_lock
);
1019 for (GeoIPDomain dom
: s_domains
) {
1020 if (dom
.domain
== name
) {
1023 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
| REG_EXTENDED
);
1024 ostringstream pathname
;
1025 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
1027 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
1028 for (size_t i
= 0; i
< glob_result
.gl_pathc
; i
++) {
1029 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
1030 auto kid
= pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[3].rm_so
);
1032 if (unlink(glob_result
.gl_pathv
[i
])) {
1033 cerr
<< "Cannot delete key:" << strerror(errno
) << endl
;
1041 globfree(&glob_result
);
1048 bool GeoIPBackend::addDomainKey(const DNSName
& name
, const KeyData
& key
, int64_t& id
)
1052 WriteLock
rl(&s_state_lock
);
1053 unsigned int nextid
= 1;
1055 for (GeoIPDomain dom
: s_domains
) {
1056 if (dom
.domain
== name
) {
1059 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
| REG_EXTENDED
);
1060 ostringstream pathname
;
1061 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
1063 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
1064 for (size_t i
= 0; i
< glob_result
.gl_pathc
; i
++) {
1065 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
1066 auto kid
= pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[3].rm_so
);
1073 globfree(&glob_result
);
1075 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "." << key
.flags
<< "." << nextid
<< "." << (key
.active
? "1" : "0") << ".key";
1076 ofstream
ofs(pathname
.str().c_str());
1077 ofs
.write(key
.content
.c_str(), key
.content
.size());
1086 bool GeoIPBackend::activateDomainKey(const DNSName
& name
, unsigned int id
)
1090 WriteLock
rl(&s_state_lock
);
1091 for (GeoIPDomain dom
: s_domains
) {
1092 if (dom
.domain
== name
) {
1095 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
| REG_EXTENDED
);
1096 ostringstream pathname
;
1097 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
1099 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
1100 for (size_t i
= 0; i
< glob_result
.gl_pathc
; i
++) {
1101 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
1102 auto kid
= pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[3].rm_so
);
1103 if (kid
== id
&& !strcmp(glob_result
.gl_pathv
[i
] + regm
[4].rm_so
, "0")) {
1104 ostringstream newpath
;
1105 newpath
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "." << pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[2].rm_so
) << "." << kid
<< ".1.key";
1106 if (rename(glob_result
.gl_pathv
[i
], newpath
.str().c_str())) {
1107 cerr
<< "Cannot activate key: " << strerror(errno
) << endl
;
1113 globfree(&glob_result
);
1121 bool GeoIPBackend::deactivateDomainKey(const DNSName
& name
, unsigned int id
)
1125 WriteLock
rl(&s_state_lock
);
1126 for (GeoIPDomain dom
: s_domains
) {
1127 if (dom
.domain
== name
) {
1130 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
| REG_EXTENDED
);
1131 ostringstream pathname
;
1132 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
1134 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
1135 for (size_t i
= 0; i
< glob_result
.gl_pathc
; i
++) {
1136 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
1137 auto kid
= pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[3].rm_so
);
1138 if (kid
== id
&& !strcmp(glob_result
.gl_pathv
[i
] + regm
[4].rm_so
, "1")) {
1139 ostringstream newpath
;
1140 newpath
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "." << pdns::checked_stoi
<unsigned int>(glob_result
.gl_pathv
[i
] + regm
[2].rm_so
) << "." << kid
<< ".0.key";
1141 if (rename(glob_result
.gl_pathv
[i
], newpath
.str().c_str())) {
1142 cerr
<< "Cannot deactivate key: " << strerror(errno
) << endl
;
1148 globfree(&glob_result
);
1156 bool GeoIPBackend::publishDomainKey(const DNSName
& /* name */, unsigned int /* id */)
1161 bool GeoIPBackend::unpublishDomainKey(const DNSName
& /* name */, unsigned int /* id */)
1166 bool GeoIPBackend::hasDNSSECkey(const DNSName
& name
)
1168 ostringstream pathname
;
1169 pathname
<< getArg("dnssec-keydir") << "/" << name
.toStringNoDot() << "*.key";
1171 if (glob(pathname
.str().c_str(), GLOB_ERR
, nullptr, &glob_result
) == 0) {
1172 globfree(&glob_result
);
1178 class GeoIPFactory
: public BackendFactory
1182 BackendFactory("geoip") {}
1184 void declareArguments(const string
& suffix
= "") override
1186 declare(suffix
, "zones-file", "YAML file to load zone(s) configuration", "");
1187 declare(suffix
, "database-files", "File(s) to load geoip data from ([driver:]path[;opt=value]", "");
1188 declare(suffix
, "dnssec-keydir", "Directory to hold dnssec keys (also turns DNSSEC on)", "");
1191 DNSBackend
* make(const string
& suffix
) override
1193 return new GeoIPBackend(suffix
);
1202 BackendMakers().report(std::make_unique
<GeoIPFactory
>());
1203 g_log
<< Logger::Info
<< "[geoipbackend] This is the geoip backend version " VERSION
1204 #ifndef REPRODUCIBLE
1205 << " (" __DATE__
" " __TIME__
")"
1207 << " reporting" << endl
;
1211 static GeoIPLoader geoiploader
;