]> git.ipfire.org Git - thirdparty/pdns.git/blob - modules/geoipbackend/geoipbackend.cc
Merge pull request #15791 from miodvallat/udon
[thirdparty/pdns.git] / modules / geoipbackend / geoipbackend.cc
1 /*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
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.
8 *
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.
12 *
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.
17 *
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.
21 */
22 #include <cstdint>
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 #include "geoipbackend.hh"
27 #include "geoipinterface.hh"
28 #include "pdns/dns_random.hh"
29 #include <sstream>
30 #include <regex.h>
31 #include <glob.h>
32 #include <boost/algorithm/string/replace.hpp>
33 #include <boost/format.hpp>
34 #include <fstream>
35 #include <filesystem>
36 #include <utility>
37 #pragma GCC diagnostic push
38 #pragma GCC diagnostic ignored "-Wshadow"
39 #include <yaml-cpp/yaml.h>
40 #pragma GCC diagnostic pop
41
42 ReadWriteLock GeoIPBackend::s_state_lock;
43
44 struct GeoIPDNSResourceRecord : DNSResourceRecord
45 {
46 int weight{};
47 bool has_weight{};
48 };
49
50 struct GeoIPService
51 {
52 NetmaskTree<vector<string>> masks;
53 unsigned int netmask4;
54 unsigned int netmask6;
55 };
56
57 struct GeoIPDomain
58 {
59 domainid_t id{};
60 ZoneName domain;
61 int ttl{};
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;
66 };
67
68 static vector<GeoIPDomain> s_domains;
69 static int s_rc = 0; // refcount - always accessed under lock
70
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"};
73
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.
76
77 If a service makes an internal reference to a domain also hosted within geoip, we give a direct
78 answers, no CNAMEs involved.
79
80 If the reference is external, we spoof up a CNAME, and good luck with that
81 */
82
83 struct DirPtrDeleter
84 {
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
88 better inlining)
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))
91 */
92 void operator()(DIR* dirPtr) const noexcept
93 {
94 closedir(dirPtr);
95 }
96 };
97
98 using UniqueDirPtr = std::unique_ptr<DIR, DirPtrDeleter>;
99
100 GeoIPBackend::GeoIPBackend(const string& suffix)
101 {
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()));
106 if (!dirHandle) {
107 throw PDNSException("dnssec-keydir " + getArg("dnssec-keydir") + " does not exist");
108 }
109 d_dnssec = true;
110 }
111 if (s_rc == 0) { // first instance gets to open everything
112 initialize();
113 }
114 s_rc++;
115 }
116
117 static vector<std::unique_ptr<GeoIPInterface>> s_geoip_files;
118
119 string getGeoForLua(const std::string& ip, int qaint);
120 static string queryGeoIP(const Netmask& addr, GeoIPInterface::GeoIPQueryAttribute attribute, GeoIPNetmask& gl);
121
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)
126 {
127 string::size_type cur = 0;
128 string::size_type last = 0;
129
130 for (const auto& lookupFormat : formats) {
131 last = 0;
132 while ((cur = lookupFormat.find("%", last)) != string::npos) {
133 if (lookupFormat.compare(cur, 3, "%mp") == 0) {
134 return false;
135 }
136
137 if (lookupFormat.compare(cur, 2, "%%") == 0) { // Ensure escaped % is also accepted
138 last = cur + 2;
139 continue;
140 }
141 last = cur + 1; // move to next attribute
142 }
143 }
144 return true;
145 }
146
147 static vector<GeoIPDNSResourceRecord> makeDNSResourceRecord(GeoIPDomain& dom, DNSName name)
148 {
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);
160 return rrs;
161 }
162
163 void GeoIPBackend::setupNetmasks(const YAML::Node& domain, GeoIPDomain& dom)
164 {
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;
170
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>>();
177 }
178 else {
179 value.push_back(net->second.as<string>());
180 }
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);
184 }
185 else {
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();
190 }
191 if (!netmask.isIPv6() && netmask4 < netmask.getBits()) {
192 netmask4 = netmask.getBits();
193 }
194 }
195 }
196 }
197 else {
198 vector<string> value;
199 if (service->second.IsSequence()) {
200 value = service->second.as<vector<string>>();
201 }
202 else {
203 value.push_back(service->second.as<string>());
204 }
205 netmaskTree.insert(Netmask("0.0.0.0/0")).second.assign(value.begin(), value.end());
206 netmaskTree.insert(Netmask("::/0")).second.swap(value);
207 }
208
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());
215 }
216
217 dom.mapping_lookup_formats = std::move(mapping_lookup_formats);
218 }
219 else {
220 dom.mapping_lookup_formats = d_global_mapping_lookup_formats;
221 }
222 if (YAML::Node mapping = domain["custom_mapping"]) {
223 dom.custom_mapping = mapping.as<map<std::string, std::string>>();
224 }
225 else {
226 dom.custom_mapping = d_global_custom_mapping;
227 }
228
229 dom.services[serviceName].netmask4 = netmask4;
230 dom.services[serviceName].netmask6 = netmask6;
231 dom.services[serviceName].masks.swap(netmaskTree);
232 }
233 }
234
235 bool GeoIPBackend::loadDomain(const YAML::Node& domain, domainid_t domainID, GeoIPDomain& dom)
236 {
237 try {
238 dom.id = domainID;
239 dom.domain = ZoneName(domain["domain"].as<string>());
240 dom.ttl = domain["ttl"].as<int>();
241
242 for (auto recs = domain["records"].begin(); recs != domain["records"].end(); recs++) {
243 ZoneName qname = ZoneName(recs->first.as<string>());
244 vector<GeoIPDNSResourceRecord> rrs;
245
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;
250 rr.ttl = dom.ttl;
251 rr.qname = qname.operator const DNSName&();
252 if (rec->first.IsNull()) {
253 rr.qtype = QType(0);
254 }
255 else {
256 string qtype = boost::to_upper_copy(rec->first.as<string>());
257 rr.qtype = qtype;
258 }
259 rr.has_weight = false;
260 rr.weight = 100;
261 if (rec->second.IsNull()) {
262 rr.content = "";
263 }
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);
270 }
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());
276 }
277 rr.has_weight = true;
278 }
279 else if (attr == "ttl") {
280 rr.ttl = iter->second.as<int>();
281 }
282 else {
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());
285 }
286 }
287 }
288 else {
289 auto content = rec->second.as<string>();
290 rr.content = std::move(content);
291 rr.weight = 100;
292 }
293 rr.auth = true;
294 rrs.push_back(rr);
295 }
296 std::swap(dom.records[qname.operator const DNSName&()], rrs);
297 }
298
299 setupNetmasks(domain, dom);
300
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);
309 }
310 }
311 }
312
313 // then services
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);
321 }
322 }
323 }
324
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) {
335 has_weight = true;
336 }
337 }
338 if (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;
346 }
347 // remove rounding gap
348 for (auto& x : lasts) {
349 float sum = sums[x.first];
350 if (sum < 1000) {
351 x.second->weight += (1000 - sum);
352 }
353 }
354 }
355 }
356 }
357 catch (std::exception& ex) {
358 g_log << Logger::Error << ex.what() << endl;
359 return false;
360 }
361 catch (PDNSException& ex) {
362 g_log << Logger::Error << ex.reason << endl;
363 return false;
364 }
365 return true;
366 }
367
368 void GeoIPBackend::loadDomainsFromDirectory(const std::string& dir, vector<GeoIPDomain>& domains)
369 {
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")) {
373 paths.push_back(p);
374 }
375 }
376 std::sort(paths.begin(), paths.end());
377 for (const auto& p : paths) {
378 try {
379 GeoIPDomain dom;
380 const auto& zoneRoot = YAML::LoadFile(p.string());
381 // expect zone key
382 const auto& zone = zoneRoot["zone"];
383 if (loadDomain(zone, domains.size(), dom)) {
384 domains.push_back(dom);
385 }
386 }
387 catch (std::exception& ex) {
388 g_log << Logger::Warning << "Cannot load zone from " << p << ": " << ex.what() << endl;
389 }
390 }
391 }
392
393 void GeoIPBackend::initialize()
394 {
395 YAML::Node config;
396 vector<GeoIPDomain> tmp_domains;
397
398 s_geoip_files.clear(); // reset pointers
399
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));
405 }
406 }
407
408 if (s_geoip_files.empty()) {
409 g_log << Logger::Warning << "No GeoIP database files loaded!" << endl;
410 }
411
412 std::string zonesFile{getArg("zones-file")};
413 if (!zonesFile.empty()) {
414 try {
415 config = YAML::LoadFile(zonesFile);
416 }
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);
421 }
422 else {
423 description = "Cannot read config file " + zonesFile;
424 }
425 throw PDNSException(description + ": " + ex.msg);
426 }
427 }
428
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"));
435 }
436 }
437 if (YAML::Node mapping = config["custom_mapping"]) {
438 d_global_custom_mapping = mapping.as<map<std::string, std::string>>();
439 }
440
441 for (YAML::const_iterator _domain = config["domains"].begin(); _domain != config["domains"].end(); _domain++) {
442 GeoIPDomain dom;
443 if (loadDomain(*_domain, tmp_domains.size(), dom)) {
444 tmp_domains.push_back(std::move(dom));
445 }
446 }
447
448 if (YAML::Node domain_dir = config["zones_dir"]) {
449 loadDomainsFromDirectory(domain_dir.as<string>(), tmp_domains);
450 }
451
452 s_domains.clear();
453 std::swap(s_domains, tmp_domains);
454
455 extern std::function<std::string(const std::string& ip, int)> g_getGeo;
456 g_getGeo = getGeoForLua;
457 }
458
459 GeoIPBackend::~GeoIPBackend()
460 {
461 try {
462 WriteLock writeLock(&s_state_lock);
463 s_rc--;
464 if (s_rc == 0) { // last instance gets to cleanup
465 s_geoip_files.clear();
466 s_domains.clear();
467 }
468 }
469 catch (...) {
470 }
471 }
472
473 bool GeoIPBackend::lookup_static(const GeoIPDomain& dom, const DNSName& search, const QType& qtype, const DNSName& qdomain, const Netmask& addr, GeoIPNetmask& gl)
474 {
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
479
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()])
483 continue;
484
485 if (rr.has_weight) {
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))
490 continue;
491 }
492 const string& content = format2str(rr.content, addr, gl, dom);
493 if (rr.qtype != QType::ENT && rr.qtype != QType::TXT && content.empty())
494 continue;
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.
500 if (rr.has_weight)
501 weighted_match[rr.qtype.getCode()] = true;
502 }
503 // ensure we get most strict netmask
504 for (DNSResourceRecord& rr : d_result) {
505 rr.scopeMask = gl.netmask;
506 }
507 return true; // no need to go further
508 }
509
510 return false;
511 };
512
513 void GeoIPBackend::lookup(const QType& qtype, const DNSName& qdomain, domainid_t zoneId, DNSPacket* pkt_p)
514 {
515 ReadLock rl(&s_state_lock);
516 const GeoIPDomain* dom;
517 GeoIPNetmask gl;
518 bool found = false;
519
520 if (d_result.size() > 0)
521 throw PDNSException("Cannot perform lookup while another is running");
522
523 d_result.clear();
524
525 if (zoneId >= 0 && zoneId < static_cast<domainid_t>(s_domains.size())) {
526 dom = &(s_domains[zoneId]);
527 }
528 else {
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)) {
531 dom = &i;
532 found = true;
533 break;
534 }
535 }
536 if (!found)
537 return; // not found
538 }
539
540 Netmask addr{"0.0.0.0/0"};
541 if (pkt_p != nullptr) {
542 addr = Netmask(pkt_p->getRealRemote());
543 }
544
545 gl.netmask = 0;
546
547 (void)this->lookup_static(*dom, qdomain, qtype, qdomain, addr, gl);
548
549 const auto& target = (*dom).services.find(qdomain);
550 if (target == (*dom).services.end())
551 return; // no hit
552
553 const NetmaskTree<vector<string>>::node_type* node = target->second.masks.lookup(addr);
554 if (node == nullptr)
555 return; // no hit, again.
556
557 DNSName sformat;
558 gl.netmask = node->first.getBits();
559 // figure out smallest sensible netmask
560 if (gl.netmask == 0) {
561 GeoIPNetmask tmp_gl;
562 tmp_gl.netmask = 0;
563 // get netmask from geoip backend
564 if (queryGeoIP(addr, GeoIPInterface::Name, tmp_gl) == "unknown") {
565 if (addr.isIPv6())
566 gl.netmask = target->second.netmask6;
567 else
568 gl.netmask = target->second.netmask4;
569 }
570 }
571 else {
572 if (addr.isIPv6())
573 gl.netmask = target->second.netmask6;
574 else
575 gl.netmask = target->second.netmask4;
576 }
577
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));
581
582 // see if the record can be found
583 if (this->lookup_static((*dom), sformat, qtype, qdomain, addr, gl))
584 return;
585 }
586
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;
591 d_result.clear();
592 return;
593 }
594
595 // we need this line since we otherwise claim to have NS records etc
596 if (!(qtype == QType::ANY || qtype == QType::CNAME))
597 return;
598
599 DNSResourceRecord rr;
600 rr.domain_id = dom->id;
601 rr.qtype = QType::CNAME;
602 rr.qname = qdomain;
603 rr.content = sformat.toString();
604 rr.auth = 1;
605 rr.ttl = dom->ttl;
606 rr.scopeMask = gl.netmask;
607 d_result.push_back(rr);
608 }
609
610 bool GeoIPBackend::get(DNSResourceRecord& r)
611 {
612 if (d_result.empty()) {
613 return false;
614 }
615
616 r = d_result.back();
617 d_result.pop_back();
618
619 return true;
620 }
621
622 static string queryGeoIP(const Netmask& addr, GeoIPInterface::GeoIPQueryAttribute attribute, GeoIPNetmask& gl)
623 {
624 string ret = "unknown";
625
626 for (auto const& gi : s_geoip_files) {
627 string val;
628 const string ip = addr.toStringNoMask();
629 bool found = false;
630
631 switch (attribute) {
632 case GeoIPInterface::ASn:
633 if (addr.isIPv6())
634 found = gi->queryASnumV6(val, gl, ip);
635 else
636 found = gi->queryASnum(val, gl, ip);
637 break;
638 case GeoIPInterface::Name:
639 if (addr.isIPv6())
640 found = gi->queryNameV6(val, gl, ip);
641 else
642 found = gi->queryName(val, gl, ip);
643 break;
644 case GeoIPInterface::Continent:
645 if (addr.isIPv6())
646 found = gi->queryContinentV6(val, gl, ip);
647 else
648 found = gi->queryContinent(val, gl, ip);
649 break;
650 case GeoIPInterface::Region:
651 if (addr.isIPv6())
652 found = gi->queryRegionV6(val, gl, ip);
653 else
654 found = gi->queryRegion(val, gl, ip);
655 break;
656 case GeoIPInterface::Country:
657 if (addr.isIPv6())
658 found = gi->queryCountryV6(val, gl, ip);
659 else
660 found = gi->queryCountry(val, gl, ip);
661 break;
662 case GeoIPInterface::Country2:
663 if (addr.isIPv6())
664 found = gi->queryCountry2V6(val, gl, ip);
665 else
666 found = gi->queryCountry2(val, gl, ip);
667 break;
668 case GeoIPInterface::City:
669 if (addr.isIPv6())
670 found = gi->queryCityV6(val, gl, ip);
671 else
672 found = gi->queryCity(val, gl, ip);
673 break;
674 case GeoIPInterface::Location:
675 double lat = 0, lon = 0;
676 std::optional<int> alt;
677 std::optional<int> prec;
678 if (addr.isIPv6())
679 found = gi->queryLocationV6(gl, ip, lat, lon, alt, prec);
680 else
681 found = gi->queryLocation(gl, ip, lat, lon, alt, prec);
682 val = std::to_string(lat) + " " + std::to_string(lon);
683 break;
684 }
685
686 if (!found || val.empty() || val == "--") {
687 continue; // try next database
688 }
689 ret = std::move(val);
690 std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower);
691 break;
692 }
693
694 if (ret == "unknown")
695 gl.netmask = (addr.isIPv6() ? 128 : 32); // prevent caching
696 return ret;
697 }
698
699 string getGeoForLua(const std::string& ip, int qaint)
700 {
701 GeoIPInterface::GeoIPQueryAttribute qa((GeoIPInterface::GeoIPQueryAttribute)qaint);
702 try {
703 const Netmask addr{ip};
704 GeoIPNetmask gl;
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);
709 return res;
710 }
711 catch (std::exception& e) {
712 cout << "Error: " << e.what() << endl;
713 }
714 catch (PDNSException& e) {
715 cout << "Error: " << e.reason << endl;
716 }
717 return "";
718 }
719
720 static bool queryGeoLocation(const Netmask& addr, GeoIPNetmask& gl, double& lat, double& lon,
721 std::optional<int>& alt, std::optional<int>& prec)
722 {
723 for (auto const& gi : s_geoip_files) {
724 string val;
725 if (addr.isIPv6()) {
726 if (gi->queryLocationV6(gl, addr.toStringNoMask(), lat, lon, alt, prec))
727 return true;
728 }
729 else if (gi->queryLocation(gl, addr.toStringNoMask(), lat, lon, alt, prec))
730 return true;
731 }
732 return false;
733 }
734
735 string GeoIPBackend::format2str(string sformat, const Netmask& addr, GeoIPNetmask& gl, const GeoIPDomain& dom)
736 {
737 string::size_type cur, last;
738 std::optional<int> alt;
739 std::optional<int> prec;
740 double lat, lon;
741 time_t t = time(nullptr);
742 GeoIPNetmask tmp_gl; // largest wins
743 struct tm gtm;
744 gmtime_r(&t, &gtm);
745 last = 0;
746
747 while ((cur = sformat.find('%', last)) != string::npos) {
748 string rep;
749 int nrep = 3;
750 tmp_gl.netmask = 0;
751 if (!sformat.compare(cur, 3, "%mp")) {
752 rep = "unknown";
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()) {
756 rep = it->second;
757 break;
758 }
759 }
760 }
761 else if (!sformat.compare(cur, 3, "%cn")) {
762 rep = queryGeoIP(addr, GeoIPInterface::Continent, tmp_gl);
763 }
764 else if (!sformat.compare(cur, 3, "%co")) {
765 rep = queryGeoIP(addr, GeoIPInterface::Country, tmp_gl);
766 }
767 else if (!sformat.compare(cur, 3, "%cc")) {
768 rep = queryGeoIP(addr, GeoIPInterface::Country2, tmp_gl);
769 }
770 else if (!sformat.compare(cur, 3, "%af")) {
771 rep = (addr.isIPv6() ? "v6" : "v4");
772 }
773 else if (!sformat.compare(cur, 3, "%as")) {
774 rep = queryGeoIP(addr, GeoIPInterface::ASn, tmp_gl);
775 }
776 else if (!sformat.compare(cur, 3, "%re")) {
777 rep = queryGeoIP(addr, GeoIPInterface::Region, tmp_gl);
778 }
779 else if (!sformat.compare(cur, 3, "%na")) {
780 rep = queryGeoIP(addr, GeoIPInterface::Name, tmp_gl);
781 }
782 else if (!sformat.compare(cur, 3, "%ci")) {
783 rep = queryGeoIP(addr, GeoIPInterface::City, tmp_gl);
784 }
785 else if (!sformat.compare(cur, 4, "%loc")) {
786 char ns, ew;
787 int d1, d2, m1, m2;
788 double s1, s2;
789 if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) {
790 rep = "";
791 tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
792 }
793 else {
794 ns = (lat > 0) ? 'N' : 'S';
795 ew = (lon > 0) ? 'E' : 'W';
796 /* remove sign */
797 lat = fabs(lat);
798 lon = fabs(lon);
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);
806 if (alt)
807 rep = rep + str(boost::format(" %d.00") % *alt);
808 else
809 rep = rep + string(" 0.00");
810 if (prec)
811 rep = rep + str(boost::format(" %dm") % *prec);
812 }
813 nrep = 4;
814 }
815 else if (!sformat.compare(cur, 4, "%lat")) {
816 if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) {
817 rep = "";
818 tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
819 }
820 else {
821 rep = str(boost::format("%lf") % lat);
822 }
823 nrep = 4;
824 }
825 else if (!sformat.compare(cur, 4, "%lon")) {
826 if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) {
827 rep = "";
828 tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
829 }
830 else {
831 rep = str(boost::format("%lf") % lon);
832 }
833 nrep = 4;
834 }
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);
838 }
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);
842 }
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);
846 }
847 else if (!sformat.compare(cur, 4, "%wds")) {
848 nrep = 4;
849 rep = GeoIP_WEEKDAYS.at(gtm.tm_wday);
850 tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
851 }
852 else if (!sformat.compare(cur, 4, "%mos")) {
853 nrep = 4;
854 rep = GeoIP_MONTHS.at(gtm.tm_mon);
855 tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
856 }
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);
860 }
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);
864 }
865 else if (!sformat.compare(cur, 4, "%ip6")) {
866 nrep = 4;
867 if (addr.isIPv6())
868 rep = addr.toStringNoMask();
869 else
870 rep = "";
871 tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
872 }
873 else if (!sformat.compare(cur, 4, "%ip4")) {
874 nrep = 4;
875 if (!addr.isIPv6())
876 rep = addr.toStringNoMask();
877 else
878 rep = "";
879 tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
880 }
881 else if (!sformat.compare(cur, 3, "%ip")) {
882 rep = addr.toStringNoMask();
883 tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
884 }
885 else if (!sformat.compare(cur, 2, "%%")) {
886 last = cur + 2;
887 continue;
888 }
889 else {
890 last = cur + 1;
891 continue;
892 }
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
897 }
898 return sformat;
899 }
900
901 void GeoIPBackend::reload()
902 {
903 WriteLock wl(&s_state_lock);
904
905 try {
906 initialize();
907 }
908 catch (PDNSException& pex) {
909 g_log << Logger::Error << "GeoIP backend reload failed: " << pex.reason << endl;
910 }
911 catch (std::exception& stex) {
912 g_log << Logger::Error << "GeoIP backend reload failed: " << stex.what() << endl;
913 }
914 catch (...) {
915 g_log << Logger::Error << "GeoIP backend reload failed" << endl;
916 }
917 }
918
919 void GeoIPBackend::rediscover(string* /* status */)
920 {
921 reload();
922 }
923
924 bool GeoIPBackend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool /* getSerial */)
925 {
926 ReadLock rl(&s_state_lock);
927
928 for (const GeoIPDomain& dom : s_domains) {
929 if (dom.domain == domain) {
930 SOAData sd;
931 this->getSOA(dom.domain, dom.id, sd);
932 info.id = dom.id;
933 info.zone = dom.domain;
934 info.serial = sd.serial;
935 info.kind = DomainInfo::Native;
936 info.backend = this;
937 return true;
938 }
939 }
940 return false;
941 }
942
943 void GeoIPBackend::getAllDomains(vector<DomainInfo>* domains, bool /* getSerial */, bool /* include_disabled */)
944 {
945 ReadLock rl(&s_state_lock);
946
947 DomainInfo di;
948 for (const auto& dom : s_domains) {
949 SOAData sd;
950 this->getSOA(dom.domain, dom.id, sd);
951 di.id = dom.id;
952 di.zone = dom.domain;
953 di.serial = sd.serial;
954 di.kind = DomainInfo::Native;
955 di.backend = this;
956 domains->emplace_back(di);
957 }
958 }
959
960 bool GeoIPBackend::getAllDomainMetadata(const ZoneName& name, std::map<std::string, std::vector<std::string>>& meta)
961 {
962 if (!d_dnssec)
963 return false;
964
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 -");
971 }
972 return true;
973 }
974 }
975 return false;
976 }
977
978 bool GeoIPBackend::getDomainMetadata(const ZoneName& name, const std::string& kind, std::vector<std::string>& meta)
979 {
980 if (!d_dnssec)
981 return false;
982
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"));
991 }
992 return true;
993 }
994 }
995 return false;
996 }
997
998 bool GeoIPBackend::getDomainKeys(const ZoneName& name, std::vector<DNSBackend::KeyData>& keys)
999 {
1000 if (!d_dnssec)
1001 return false;
1002 ReadLock rl(&s_state_lock);
1003 for (const GeoIPDomain& dom : s_domains) {
1004 if (dom.domain == name) {
1005 regex_t reg;
1006 regmatch_t regm[5];
1007 regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE | REG_EXTENDED);
1008 ostringstream pathname;
1009 pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
1010 glob_t glob_result;
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(&reg, 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;
1021 char buffer[1024];
1022 while (ifs.good()) {
1023 ifs.read(buffer, sizeof buffer);
1024 if (ifs.gcount() > 0) {
1025 content << string(buffer, ifs.gcount());
1026 }
1027 }
1028 ifs.close();
1029 kd.content = content.str();
1030 keys.emplace_back(std::move(kd));
1031 }
1032 }
1033 }
1034 regfree(&reg);
1035 globfree(&glob_result);
1036 return true;
1037 }
1038 }
1039 return false;
1040 }
1041
1042 bool GeoIPBackend::removeDomainKey(const ZoneName& name, unsigned int keyId)
1043 {
1044 if (!d_dnssec)
1045 return false;
1046 WriteLock rl(&s_state_lock);
1047 ostringstream path;
1048
1049 for (const GeoIPDomain& dom : s_domains) {
1050 if (dom.domain == name) {
1051 regex_t reg;
1052 regmatch_t regm[5];
1053 regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE | REG_EXTENDED);
1054 ostringstream pathname;
1055 pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
1056 glob_t glob_result;
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(&reg, 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);
1061 if (kid == keyId) {
1062 if (unlink(glob_result.gl_pathv[i])) {
1063 cerr << "Cannot delete key:" << strerror(errno) << endl;
1064 }
1065 break;
1066 }
1067 }
1068 }
1069 }
1070 regfree(&reg);
1071 globfree(&glob_result);
1072 return true;
1073 }
1074 }
1075 return false;
1076 }
1077
1078 bool GeoIPBackend::addDomainKey(const ZoneName& name, const KeyData& key, int64_t& keyId)
1079 {
1080 if (!d_dnssec)
1081 return false;
1082 WriteLock rl(&s_state_lock);
1083 unsigned int nextid = 1;
1084
1085 for (const GeoIPDomain& dom : s_domains) {
1086 if (dom.domain == name) {
1087 regex_t reg;
1088 regmatch_t regm[5];
1089 regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE | REG_EXTENDED);
1090 ostringstream pathname;
1091 pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
1092 glob_t glob_result;
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(&reg, 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);
1097 if (kid >= nextid)
1098 nextid = kid + 1;
1099 }
1100 }
1101 }
1102 regfree(&reg);
1103 globfree(&glob_result);
1104 pathname.str("");
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());
1108 ofs.close();
1109 keyId = nextid;
1110 return true;
1111 }
1112 }
1113 return false;
1114 }
1115
1116 bool GeoIPBackend::activateDomainKey(const ZoneName& name, unsigned int keyId)
1117 {
1118 if (!d_dnssec)
1119 return false;
1120 WriteLock rl(&s_state_lock);
1121 for (const GeoIPDomain& dom : s_domains) {
1122 if (dom.domain == name) {
1123 regex_t reg;
1124 regmatch_t regm[5];
1125 regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE | REG_EXTENDED);
1126 ostringstream pathname;
1127 pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
1128 glob_t glob_result;
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(&reg, 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;
1138 }
1139 }
1140 }
1141 }
1142 }
1143 globfree(&glob_result);
1144 regfree(&reg);
1145 return true;
1146 }
1147 }
1148 return false;
1149 }
1150
1151 bool GeoIPBackend::deactivateDomainKey(const ZoneName& name, unsigned int keyId)
1152 {
1153 if (!d_dnssec)
1154 return false;
1155 WriteLock rl(&s_state_lock);
1156 for (const GeoIPDomain& dom : s_domains) {
1157 if (dom.domain == name) {
1158 regex_t reg;
1159 regmatch_t regm[5];
1160 regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE | REG_EXTENDED);
1161 ostringstream pathname;
1162 pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
1163 glob_t glob_result;
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(&reg, 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;
1173 }
1174 }
1175 }
1176 }
1177 }
1178 globfree(&glob_result);
1179 regfree(&reg);
1180 return true;
1181 }
1182 }
1183 return false;
1184 }
1185
1186 bool GeoIPBackend::publishDomainKey(const ZoneName& /* name */, unsigned int /* id */)
1187 {
1188 return false;
1189 }
1190
1191 bool GeoIPBackend::unpublishDomainKey(const ZoneName& /* name */, unsigned int /* id */)
1192 {
1193 return false;
1194 }
1195
1196 bool GeoIPBackend::hasDNSSECkey(const ZoneName& name)
1197 {
1198 ostringstream pathname;
1199 pathname << getArg("dnssec-keydir") << "/" << name.toStringNoDot() << "*.key";
1200 glob_t glob_result;
1201 if (glob(pathname.str().c_str(), GLOB_ERR, nullptr, &glob_result) == 0) {
1202 globfree(&glob_result);
1203 return true;
1204 }
1205 return false;
1206 }
1207
1208 class GeoIPFactory : public BackendFactory
1209 {
1210 public:
1211 GeoIPFactory() :
1212 BackendFactory("geoip") {}
1213
1214 void declareArguments(const string& suffix = "") override
1215 {
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)", "");
1219 }
1220
1221 DNSBackend* make(const string& suffix) override
1222 {
1223 return new GeoIPBackend(suffix);
1224 }
1225 };
1226
1227 class GeoIPLoader
1228 {
1229 public:
1230 GeoIPLoader()
1231 {
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__ ")"
1236 #endif
1237 << " reporting" << endl;
1238 }
1239 };
1240
1241 static GeoIPLoader geoiploader;