]> git.ipfire.org Git - thirdparty/pdns.git/blob - modules/geoipbackend/geoipbackend.cc
Merge pull request #13387 from omoerbeek/rec-b-root-servers
[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 std::uint32_t id{};
60 DNSName 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 GeoIPBackend::GeoIPBackend(const string& suffix)
84 {
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);
89 if (!dirHandle) {
90 throw PDNSException("dnssec-keydir " + getArg("dnssec-keydir") + " does not exist");
91 }
92 d_dnssec = true;
93 }
94 if (s_rc == 0) { // first instance gets to open everything
95 initialize();
96 }
97 s_rc++;
98 }
99
100 static vector<std::unique_ptr<GeoIPInterface>> s_geoip_files;
101
102 string getGeoForLua(const std::string& ip, int qaint);
103 static string queryGeoIP(const Netmask& addr, GeoIPInterface::GeoIPQueryAttribute attribute, GeoIPNetmask& gl);
104
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)
109 {
110 string::size_type cur = 0;
111 string::size_type last = 0;
112
113 for (const auto& lookupFormat : formats) {
114 last = 0;
115 while ((cur = lookupFormat.find("%", last)) != string::npos) {
116 if (lookupFormat.compare(cur, 3, "%mp") == 0) {
117 return false;
118 }
119
120 if (lookupFormat.compare(cur, 2, "%%") == 0) { // Ensure escaped % is also accepted
121 last = cur + 2;
122 continue;
123 }
124 last = cur + 1; // move to next attribute
125 }
126 }
127 return true;
128 }
129
130 static vector<GeoIPDNSResourceRecord> makeDNSResourceRecord(GeoIPDomain& dom, DNSName name)
131 {
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);
143 return rrs;
144 }
145
146 void GeoIPBackend::setupNetmasks(const YAML::Node& domain, GeoIPDomain& dom)
147 {
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;
153
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>>();
160 }
161 else {
162 value.push_back(net->second.as<string>());
163 }
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);
167 }
168 else {
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();
173 }
174 if (!netmask.isIPv6() && netmask4 < netmask.getBits()) {
175 netmask4 = netmask.getBits();
176 }
177 }
178 }
179 }
180 else {
181 vector<string> value;
182 if (service->second.IsSequence()) {
183 value = service->second.as<vector<string>>();
184 }
185 else {
186 value.push_back(service->second.as<string>());
187 }
188 netmaskTree.insert(Netmask("0.0.0.0/0")).second.assign(value.begin(), value.end());
189 netmaskTree.insert(Netmask("::/0")).second.swap(value);
190 }
191
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());
198 }
199
200 dom.mapping_lookup_formats = mapping_lookup_formats;
201 }
202 else {
203 dom.mapping_lookup_formats = d_global_mapping_lookup_formats;
204 }
205 if (YAML::Node mapping = domain["custom_mapping"]) {
206 dom.custom_mapping = mapping.as<map<std::string, std::string>>();
207 }
208 else {
209 dom.custom_mapping = d_global_custom_mapping;
210 }
211
212 dom.services[serviceName].netmask4 = netmask4;
213 dom.services[serviceName].netmask6 = netmask6;
214 dom.services[serviceName].masks.swap(netmaskTree);
215 }
216 }
217
218 bool GeoIPBackend::loadDomain(const YAML::Node& domain, std::uint32_t domainID, GeoIPDomain& dom)
219 {
220 try {
221 dom.id = domainID;
222 dom.domain = DNSName(domain["domain"].as<string>());
223 dom.ttl = domain["ttl"].as<int>();
224
225 for (auto recs = domain["records"].begin(); recs != domain["records"].end(); recs++) {
226 DNSName qname = DNSName(recs->first.as<string>());
227 vector<GeoIPDNSResourceRecord> rrs;
228
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);
233 rr.ttl = dom.ttl;
234 rr.qname = qname;
235 if (rec->first.IsNull()) {
236 rr.qtype = QType(0);
237 }
238 else {
239 string qtype = boost::to_upper_copy(rec->first.as<string>());
240 rr.qtype = qtype;
241 }
242 rr.has_weight = false;
243 rr.weight = 100;
244 if (rec->second.IsNull()) {
245 rr.content = "";
246 }
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);
253 }
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());
259 }
260 rr.has_weight = true;
261 }
262 else if (attr == "ttl") {
263 rr.ttl = iter->second.as<int>();
264 }
265 else {
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());
268 }
269 }
270 }
271 else {
272 auto content = rec->second.as<string>();
273 rr.content = std::move(content);
274 rr.weight = 100;
275 }
276 rr.auth = true;
277 rrs.push_back(rr);
278 }
279 std::swap(dom.records[qname], rrs);
280 }
281
282 setupNetmasks(domain, dom);
283
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);
292 }
293 }
294 }
295
296 // then services
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);
304 }
305 }
306 }
307
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) {
318 has_weight = true;
319 }
320 }
321 if (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;
329 }
330 // remove rounding gap
331 for (auto& x : lasts) {
332 float sum = sums[x.first];
333 if (sum < 1000) {
334 x.second->weight += (1000 - sum);
335 }
336 }
337 }
338 }
339 }
340 catch (std::exception& ex) {
341 g_log << Logger::Error << ex.what() << endl;
342 return false;
343 }
344 catch (PDNSException& ex) {
345 g_log << Logger::Error << ex.reason << endl;
346 return false;
347 }
348 return true;
349 }
350
351 void GeoIPBackend::loadDomainsFromDirectory(const std::string& dir, vector<GeoIPDomain>& domains)
352 {
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")) {
356 paths.push_back(p);
357 }
358 }
359 std::sort(paths.begin(), paths.end());
360 for (const auto& p : paths) {
361 try {
362 GeoIPDomain dom;
363 const auto& zoneRoot = YAML::LoadFile(p.string());
364 // expect zone key
365 const auto& zone = zoneRoot["zone"];
366 if (loadDomain(zone, domains.size(), dom)) {
367 domains.push_back(dom);
368 }
369 }
370 catch (std::exception& ex) {
371 g_log << Logger::Warning << "Cannot load zone from " << p << ": " << ex.what() << endl;
372 }
373 }
374 }
375
376 void GeoIPBackend::initialize()
377 {
378 YAML::Node config;
379 vector<GeoIPDomain> tmp_domains;
380
381 s_geoip_files.clear(); // reset pointers
382
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));
388 }
389 }
390
391 if (s_geoip_files.empty()) {
392 g_log << Logger::Warning << "No GeoIP database files loaded!" << endl;
393 }
394
395 if (!getArg("zones-file").empty()) {
396 try {
397 config = YAML::LoadFile(getArg("zones-file"));
398 }
399 catch (YAML::Exception& ex) {
400 throw PDNSException(string("Cannot read config file ") + ex.msg);
401 }
402 }
403
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"));
410 }
411 }
412 if (YAML::Node mapping = config["custom_mapping"]) {
413 d_global_custom_mapping = mapping.as<map<std::string, std::string>>();
414 }
415
416 for (YAML::const_iterator _domain = config["domains"].begin(); _domain != config["domains"].end(); _domain++) {
417 GeoIPDomain dom;
418 if (loadDomain(*_domain, tmp_domains.size(), dom)) {
419 tmp_domains.push_back(std::move(dom));
420 }
421 }
422
423 if (YAML::Node domain_dir = config["zones_dir"]) {
424 loadDomainsFromDirectory(domain_dir.as<string>(), tmp_domains);
425 }
426
427 s_domains.clear();
428 std::swap(s_domains, tmp_domains);
429
430 extern std::function<std::string(const std::string& ip, int)> g_getGeo;
431 g_getGeo = getGeoForLua;
432 }
433
434 GeoIPBackend::~GeoIPBackend()
435 {
436 try {
437 WriteLock writeLock(&s_state_lock);
438 s_rc--;
439 if (s_rc == 0) { // last instance gets to cleanup
440 s_geoip_files.clear();
441 s_domains.clear();
442 }
443 }
444 catch (...) {
445 }
446 }
447
448 bool GeoIPBackend::lookup_static(const GeoIPDomain& dom, const DNSName& search, const QType& qtype, const DNSName& qdomain, const Netmask& addr, GeoIPNetmask& gl)
449 {
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
454
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()])
458 continue;
459
460 if (rr.has_weight) {
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))
465 continue;
466 }
467 const string& content = format2str(rr.content, addr, gl, dom);
468 if (rr.qtype != QType::ENT && rr.qtype != QType::TXT && content.empty())
469 continue;
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.
475 if (rr.has_weight)
476 weighted_match[rr.qtype.getCode()] = true;
477 }
478 // ensure we get most strict netmask
479 for (DNSResourceRecord& rr : d_result) {
480 rr.scopeMask = gl.netmask;
481 }
482 return true; // no need to go further
483 }
484
485 return false;
486 };
487
488 void GeoIPBackend::lookup(const QType& qtype, const DNSName& qdomain, int zoneId, DNSPacket* pkt_p)
489 {
490 ReadLock rl(&s_state_lock);
491 const GeoIPDomain* dom;
492 GeoIPNetmask gl;
493 bool found = false;
494
495 if (d_result.size() > 0)
496 throw PDNSException("Cannot perform lookup while another is running");
497
498 d_result.clear();
499
500 if (zoneId > -1 && zoneId < static_cast<int>(s_domains.size()))
501 dom = &(s_domains[zoneId]);
502 else {
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)) {
505 dom = &i;
506 found = true;
507 break;
508 }
509 }
510 if (!found)
511 return; // not found
512 }
513
514 Netmask addr{"0.0.0.0/0"};
515 if (pkt_p != nullptr)
516 addr = Netmask(pkt_p->getRealRemote());
517
518 gl.netmask = 0;
519
520 (void)this->lookup_static(*dom, qdomain, qtype, qdomain, addr, gl);
521
522 const auto& target = (*dom).services.find(qdomain);
523 if (target == (*dom).services.end())
524 return; // no hit
525
526 const NetmaskTree<vector<string>>::node_type* node = target->second.masks.lookup(addr);
527 if (node == nullptr)
528 return; // no hit, again.
529
530 DNSName sformat;
531 gl.netmask = node->first.getBits();
532 // figure out smallest sensible netmask
533 if (gl.netmask == 0) {
534 GeoIPNetmask tmp_gl;
535 tmp_gl.netmask = 0;
536 // get netmask from geoip backend
537 if (queryGeoIP(addr, GeoIPInterface::Name, tmp_gl) == "unknown") {
538 if (addr.isIPv6())
539 gl.netmask = target->second.netmask6;
540 else
541 gl.netmask = target->second.netmask4;
542 }
543 }
544 else {
545 if (addr.isIPv6())
546 gl.netmask = target->second.netmask6;
547 else
548 gl.netmask = target->second.netmask4;
549 }
550
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));
554
555 // see if the record can be found
556 if (this->lookup_static((*dom), sformat, qtype, qdomain, addr, gl))
557 return;
558 }
559
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;
564 d_result.clear();
565 return;
566 }
567
568 // we need this line since we otherwise claim to have NS records etc
569 if (!(qtype == QType::ANY || qtype == QType::CNAME))
570 return;
571
572 DNSResourceRecord rr;
573 rr.domain_id = dom->id;
574 rr.qtype = QType::CNAME;
575 rr.qname = qdomain;
576 rr.content = sformat.toString();
577 rr.auth = 1;
578 rr.ttl = dom->ttl;
579 rr.scopeMask = gl.netmask;
580 d_result.push_back(rr);
581 }
582
583 bool GeoIPBackend::get(DNSResourceRecord& r)
584 {
585 if (d_result.empty())
586 return false;
587
588 r = d_result.back();
589 d_result.pop_back();
590
591 return true;
592 }
593
594 static string queryGeoIP(const Netmask& addr, GeoIPInterface::GeoIPQueryAttribute attribute, GeoIPNetmask& gl)
595 {
596 string ret = "unknown";
597
598 for (auto const& gi : s_geoip_files) {
599 string val;
600 const string ip = addr.toStringNoMask();
601 bool found = false;
602
603 switch (attribute) {
604 case GeoIPInterface::ASn:
605 if (addr.isIPv6())
606 found = gi->queryASnumV6(val, gl, ip);
607 else
608 found = gi->queryASnum(val, gl, ip);
609 break;
610 case GeoIPInterface::Name:
611 if (addr.isIPv6())
612 found = gi->queryNameV6(val, gl, ip);
613 else
614 found = gi->queryName(val, gl, ip);
615 break;
616 case GeoIPInterface::Continent:
617 if (addr.isIPv6())
618 found = gi->queryContinentV6(val, gl, ip);
619 else
620 found = gi->queryContinent(val, gl, ip);
621 break;
622 case GeoIPInterface::Region:
623 if (addr.isIPv6())
624 found = gi->queryRegionV6(val, gl, ip);
625 else
626 found = gi->queryRegion(val, gl, ip);
627 break;
628 case GeoIPInterface::Country:
629 if (addr.isIPv6())
630 found = gi->queryCountryV6(val, gl, ip);
631 else
632 found = gi->queryCountry(val, gl, ip);
633 break;
634 case GeoIPInterface::Country2:
635 if (addr.isIPv6())
636 found = gi->queryCountry2V6(val, gl, ip);
637 else
638 found = gi->queryCountry2(val, gl, ip);
639 break;
640 case GeoIPInterface::City:
641 if (addr.isIPv6())
642 found = gi->queryCityV6(val, gl, ip);
643 else
644 found = gi->queryCity(val, gl, ip);
645 break;
646 case GeoIPInterface::Location:
647 double lat = 0, lon = 0;
648 boost::optional<int> alt, prec;
649 if (addr.isIPv6())
650 found = gi->queryLocationV6(gl, ip, lat, lon, alt, prec);
651 else
652 found = gi->queryLocation(gl, ip, lat, lon, alt, prec);
653 val = std::to_string(lat) + " " + std::to_string(lon);
654 break;
655 }
656
657 if (!found || val.empty() || val == "--")
658 continue; // try next database
659 ret = val;
660 std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower);
661 break;
662 }
663
664 if (ret == "unknown")
665 gl.netmask = (addr.isIPv6() ? 128 : 32); // prevent caching
666 return ret;
667 }
668
669 string getGeoForLua(const std::string& ip, int qaint)
670 {
671 GeoIPInterface::GeoIPQueryAttribute qa((GeoIPInterface::GeoIPQueryAttribute)qaint);
672 try {
673 const Netmask addr{ip};
674 GeoIPNetmask gl;
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);
679 return res;
680 }
681 catch (std::exception& e) {
682 cout << "Error: " << e.what() << endl;
683 }
684 catch (PDNSException& e) {
685 cout << "Error: " << e.reason << endl;
686 }
687 return "";
688 }
689
690 static bool queryGeoLocation(const Netmask& addr, GeoIPNetmask& gl, double& lat, double& lon,
691 boost::optional<int>& alt, boost::optional<int>& prec)
692 {
693 for (auto const& gi : s_geoip_files) {
694 string val;
695 if (addr.isIPv6()) {
696 if (gi->queryLocationV6(gl, addr.toStringNoMask(), lat, lon, alt, prec))
697 return true;
698 }
699 else if (gi->queryLocation(gl, addr.toStringNoMask(), lat, lon, alt, prec))
700 return true;
701 }
702 return false;
703 }
704
705 string GeoIPBackend::format2str(string sformat, const Netmask& addr, GeoIPNetmask& gl, const GeoIPDomain& dom)
706 {
707 string::size_type cur, last;
708 boost::optional<int> alt, prec;
709 double lat, lon;
710 time_t t = time(nullptr);
711 GeoIPNetmask tmp_gl; // largest wins
712 struct tm gtm;
713 gmtime_r(&t, &gtm);
714 last = 0;
715
716 while ((cur = sformat.find('%', last)) != string::npos) {
717 string rep;
718 int nrep = 3;
719 tmp_gl.netmask = 0;
720 if (!sformat.compare(cur, 3, "%mp")) {
721 rep = "unknown";
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()) {
725 rep = it->second;
726 break;
727 }
728 }
729 }
730 else if (!sformat.compare(cur, 3, "%cn")) {
731 rep = queryGeoIP(addr, GeoIPInterface::Continent, tmp_gl);
732 }
733 else if (!sformat.compare(cur, 3, "%co")) {
734 rep = queryGeoIP(addr, GeoIPInterface::Country, tmp_gl);
735 }
736 else if (!sformat.compare(cur, 3, "%cc")) {
737 rep = queryGeoIP(addr, GeoIPInterface::Country2, tmp_gl);
738 }
739 else if (!sformat.compare(cur, 3, "%af")) {
740 rep = (addr.isIPv6() ? "v6" : "v4");
741 }
742 else if (!sformat.compare(cur, 3, "%as")) {
743 rep = queryGeoIP(addr, GeoIPInterface::ASn, tmp_gl);
744 }
745 else if (!sformat.compare(cur, 3, "%re")) {
746 rep = queryGeoIP(addr, GeoIPInterface::Region, tmp_gl);
747 }
748 else if (!sformat.compare(cur, 3, "%na")) {
749 rep = queryGeoIP(addr, GeoIPInterface::Name, tmp_gl);
750 }
751 else if (!sformat.compare(cur, 3, "%ci")) {
752 rep = queryGeoIP(addr, GeoIPInterface::City, tmp_gl);
753 }
754 else if (!sformat.compare(cur, 4, "%loc")) {
755 char ns, ew;
756 int d1, d2, m1, m2;
757 double s1, s2;
758 if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) {
759 rep = "";
760 tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
761 }
762 else {
763 ns = (lat > 0) ? 'N' : 'S';
764 ew = (lon > 0) ? 'E' : 'W';
765 /* remove sign */
766 lat = fabs(lat);
767 lon = fabs(lon);
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);
775 if (alt)
776 rep = rep + str(boost::format(" %d.00") % *alt);
777 else
778 rep = rep + string(" 0.00");
779 if (prec)
780 rep = rep + str(boost::format(" %dm") % *prec);
781 }
782 nrep = 4;
783 }
784 else if (!sformat.compare(cur, 4, "%lat")) {
785 if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) {
786 rep = "";
787 tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
788 }
789 else {
790 rep = str(boost::format("%lf") % lat);
791 }
792 nrep = 4;
793 }
794 else if (!sformat.compare(cur, 4, "%lon")) {
795 if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) {
796 rep = "";
797 tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
798 }
799 else {
800 rep = str(boost::format("%lf") % lon);
801 }
802 nrep = 4;
803 }
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);
807 }
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);
811 }
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);
815 }
816 else if (!sformat.compare(cur, 4, "%wds")) {
817 nrep = 4;
818 rep = GeoIP_WEEKDAYS.at(gtm.tm_wday);
819 tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
820 }
821 else if (!sformat.compare(cur, 4, "%mos")) {
822 nrep = 4;
823 rep = GeoIP_MONTHS.at(gtm.tm_mon);
824 tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
825 }
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);
829 }
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);
833 }
834 else if (!sformat.compare(cur, 4, "%ip6")) {
835 nrep = 4;
836 if (addr.isIPv6())
837 rep = addr.toStringNoMask();
838 else
839 rep = "";
840 tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
841 }
842 else if (!sformat.compare(cur, 4, "%ip4")) {
843 nrep = 4;
844 if (!addr.isIPv6())
845 rep = addr.toStringNoMask();
846 else
847 rep = "";
848 tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
849 }
850 else if (!sformat.compare(cur, 3, "%ip")) {
851 rep = addr.toStringNoMask();
852 tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
853 }
854 else if (!sformat.compare(cur, 2, "%%")) {
855 last = cur + 2;
856 continue;
857 }
858 else {
859 last = cur + 1;
860 continue;
861 }
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
866 }
867 return sformat;
868 }
869
870 void GeoIPBackend::reload()
871 {
872 WriteLock wl(&s_state_lock);
873
874 try {
875 initialize();
876 }
877 catch (PDNSException& pex) {
878 g_log << Logger::Error << "GeoIP backend reload failed: " << pex.reason << endl;
879 }
880 catch (std::exception& stex) {
881 g_log << Logger::Error << "GeoIP backend reload failed: " << stex.what() << endl;
882 }
883 catch (...) {
884 g_log << Logger::Error << "GeoIP backend reload failed" << endl;
885 }
886 }
887
888 void GeoIPBackend::rediscover(string* /* status */)
889 {
890 reload();
891 }
892
893 bool GeoIPBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool /* getSerial */)
894 {
895 ReadLock rl(&s_state_lock);
896
897 for (GeoIPDomain dom : s_domains) {
898 if (dom.domain == domain) {
899 SOAData sd;
900 this->getSOA(domain, sd);
901 di.id = dom.id;
902 di.zone = dom.domain;
903 di.serial = sd.serial;
904 di.kind = DomainInfo::Native;
905 di.backend = this;
906 return true;
907 }
908 }
909 return false;
910 }
911
912 void GeoIPBackend::getAllDomains(vector<DomainInfo>* domains, bool /* getSerial */, bool /* include_disabled */)
913 {
914 ReadLock rl(&s_state_lock);
915
916 DomainInfo di;
917 for (const auto& dom : s_domains) {
918 SOAData sd;
919 this->getSOA(dom.domain, sd);
920 di.id = dom.id;
921 di.zone = dom.domain;
922 di.serial = sd.serial;
923 di.kind = DomainInfo::Native;
924 di.backend = this;
925 domains->emplace_back(di);
926 }
927 }
928
929 bool GeoIPBackend::getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string>>& meta)
930 {
931 if (!d_dnssec)
932 return false;
933
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");
940 }
941 return true;
942 }
943 }
944 return false;
945 }
946
947 bool GeoIPBackend::getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta)
948 {
949 if (!d_dnssec)
950 return false;
951
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"));
960 }
961 return true;
962 }
963 }
964 return false;
965 }
966
967 bool GeoIPBackend::getDomainKeys(const DNSName& name, std::vector<DNSBackend::KeyData>& keys)
968 {
969 if (!d_dnssec)
970 return false;
971 ReadLock rl(&s_state_lock);
972 for (GeoIPDomain dom : s_domains) {
973 if (dom.domain == name) {
974 regex_t reg;
975 regmatch_t regm[5];
976 regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE | REG_EXTENDED);
977 ostringstream pathname;
978 pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
979 glob_t glob_result;
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(&reg, 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);
986 kd.published = true;
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;
990 char buffer[1024];
991 while (ifs.good()) {
992 ifs.read(buffer, sizeof buffer);
993 if (ifs.gcount() > 0) {
994 content << string(buffer, ifs.gcount());
995 }
996 }
997 ifs.close();
998 kd.content = content.str();
999 keys.push_back(kd);
1000 }
1001 }
1002 }
1003 regfree(&reg);
1004 globfree(&glob_result);
1005 return true;
1006 }
1007 }
1008 return false;
1009 }
1010
1011 bool GeoIPBackend::removeDomainKey(const DNSName& name, unsigned int id)
1012 {
1013 if (!d_dnssec)
1014 return false;
1015 WriteLock rl(&s_state_lock);
1016 ostringstream path;
1017
1018 for (GeoIPDomain dom : s_domains) {
1019 if (dom.domain == name) {
1020 regex_t reg;
1021 regmatch_t regm[5];
1022 regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE | REG_EXTENDED);
1023 ostringstream pathname;
1024 pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
1025 glob_t glob_result;
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(&reg, 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);
1030 if (kid == id) {
1031 if (unlink(glob_result.gl_pathv[i])) {
1032 cerr << "Cannot delete key:" << strerror(errno) << endl;
1033 }
1034 break;
1035 }
1036 }
1037 }
1038 }
1039 regfree(&reg);
1040 globfree(&glob_result);
1041 return true;
1042 }
1043 }
1044 return false;
1045 }
1046
1047 bool GeoIPBackend::addDomainKey(const DNSName& name, const KeyData& key, int64_t& id)
1048 {
1049 if (!d_dnssec)
1050 return false;
1051 WriteLock rl(&s_state_lock);
1052 unsigned int nextid = 1;
1053
1054 for (GeoIPDomain dom : s_domains) {
1055 if (dom.domain == name) {
1056 regex_t reg;
1057 regmatch_t regm[5];
1058 regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE | REG_EXTENDED);
1059 ostringstream pathname;
1060 pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
1061 glob_t glob_result;
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(&reg, 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);
1066 if (kid >= nextid)
1067 nextid = kid + 1;
1068 }
1069 }
1070 }
1071 regfree(&reg);
1072 globfree(&glob_result);
1073 pathname.str("");
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());
1077 ofs.close();
1078 id = nextid;
1079 return true;
1080 }
1081 }
1082 return false;
1083 }
1084
1085 bool GeoIPBackend::activateDomainKey(const DNSName& name, unsigned int id)
1086 {
1087 if (!d_dnssec)
1088 return false;
1089 WriteLock rl(&s_state_lock);
1090 for (GeoIPDomain dom : s_domains) {
1091 if (dom.domain == name) {
1092 regex_t reg;
1093 regmatch_t regm[5];
1094 regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE | REG_EXTENDED);
1095 ostringstream pathname;
1096 pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
1097 glob_t glob_result;
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(&reg, 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;
1107 }
1108 }
1109 }
1110 }
1111 }
1112 globfree(&glob_result);
1113 regfree(&reg);
1114 return true;
1115 }
1116 }
1117 return false;
1118 }
1119
1120 bool GeoIPBackend::deactivateDomainKey(const DNSName& name, unsigned int id)
1121 {
1122 if (!d_dnssec)
1123 return false;
1124 WriteLock rl(&s_state_lock);
1125 for (GeoIPDomain dom : s_domains) {
1126 if (dom.domain == name) {
1127 regex_t reg;
1128 regmatch_t regm[5];
1129 regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE | REG_EXTENDED);
1130 ostringstream pathname;
1131 pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
1132 glob_t glob_result;
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(&reg, 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;
1142 }
1143 }
1144 }
1145 }
1146 }
1147 globfree(&glob_result);
1148 regfree(&reg);
1149 return true;
1150 }
1151 }
1152 return false;
1153 }
1154
1155 bool GeoIPBackend::publishDomainKey(const DNSName& /* name */, unsigned int /* id */)
1156 {
1157 return false;
1158 }
1159
1160 bool GeoIPBackend::unpublishDomainKey(const DNSName& /* name */, unsigned int /* id */)
1161 {
1162 return false;
1163 }
1164
1165 bool GeoIPBackend::hasDNSSECkey(const DNSName& name)
1166 {
1167 ostringstream pathname;
1168 pathname << getArg("dnssec-keydir") << "/" << name.toStringNoDot() << "*.key";
1169 glob_t glob_result;
1170 if (glob(pathname.str().c_str(), GLOB_ERR, nullptr, &glob_result) == 0) {
1171 globfree(&glob_result);
1172 return true;
1173 }
1174 return false;
1175 }
1176
1177 class GeoIPFactory : public BackendFactory
1178 {
1179 public:
1180 GeoIPFactory() :
1181 BackendFactory("geoip") {}
1182
1183 void declareArguments(const string& suffix = "") override
1184 {
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)", "");
1188 }
1189
1190 DNSBackend* make(const string& suffix) override
1191 {
1192 return new GeoIPBackend(suffix);
1193 }
1194 };
1195
1196 class GeoIPLoader
1197 {
1198 public:
1199 GeoIPLoader()
1200 {
1201 BackendMakers().report(new GeoIPFactory);
1202 g_log << Logger::Info << "[geoipbackend] This is the geoip backend version " VERSION
1203 #ifndef REPRODUCIBLE
1204 << " (" __DATE__ " " __TIME__ ")"
1205 #endif
1206 << " reporting" << endl;
1207 }
1208 };
1209
1210 static GeoIPLoader geoiploader;