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.
25 #include "geoipbackend.hh"
26 #include "geoipinterface.hh"
27 #include "pdns/dns_random.hh"
31 #include <boost/algorithm/string/replace.hpp>
33 #include <yaml-cpp/yaml.h>
35 pthread_rwlock_t
GeoIPBackend::s_state_lock
=PTHREAD_RWLOCK_INITIALIZER
;
37 struct GeoIPDNSResourceRecord
: DNSResourceRecord
{
43 NetmaskTree
<vector
<string
> > masks
;
44 unsigned int netmask4
;
45 unsigned int netmask6
;
52 map
<DNSName
, GeoIPService
> services
;
53 map
<DNSName
, vector
<GeoIPDNSResourceRecord
> > records
;
56 static vector
<GeoIPDomain
> s_domains
;
57 static int s_rc
= 0; // refcount - always accessed under lock
59 static string GeoIP_WEEKDAYS
[] = { "mon", "tue", "wed", "thu", "fri", "sat", "sun" };
60 static string GeoIP_MONTHS
[] = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" };
62 /* So how does it work - we have static records and services. Static records "win".
63 We also insert empty non terminals for records and services.
65 If a service makes an internal reference to a domain also hosted within geoip, we give a direct
66 answers, no CNAMEs involved.
68 If the reference is external, we spoof up a CNAME, and good luck with that
71 GeoIPBackend::GeoIPBackend(const string
& suffix
) {
72 WriteLock
wl(&s_state_lock
);
74 setArgPrefix("geoip" + suffix
);
75 if (getArg("dnssec-keydir").empty() == false) {
76 DIR *d
= opendir(getArg("dnssec-keydir").c_str());
78 throw PDNSException("dnssec-keydir " + getArg("dnssec-keydir") + " does not exist");
83 if (s_rc
== 0) { // first instance gets to open everything
89 static vector
<std::unique_ptr
<GeoIPInterface
> > s_geoip_files
;
91 string
getGeoForLua(const std::string
& ip
, int qaint
);
92 static string
queryGeoIP(const Netmask
& addr
, GeoIPInterface::GeoIPQueryAttribute attribute
, GeoIPNetmask
& gl
);
94 void GeoIPBackend::initialize() {
96 vector
<GeoIPDomain
> tmp_domains
;
98 s_geoip_files
.clear(); // reset pointers
100 if (getArg("database-files").empty() == false) {
101 vector
<string
> files
;
102 stringtok(files
, getArg("database-files"), " ,\t\r\n");
103 for(auto const& file
: files
) {
104 s_geoip_files
.push_back(GeoIPInterface::makeInterface(file
));
108 if (s_geoip_files
.empty())
109 g_log
<<Logger::Warning
<<"No GeoIP database files loaded!"<<endl
;
111 if(!getArg("zones-file").empty()) {
113 config
= YAML::LoadFile(getArg("zones-file"));
114 } catch (YAML::Exception
&ex
) {
115 throw PDNSException(string("Cannot read config file ") + ex
.msg
);
119 for(YAML::Node domain
: config
["domains"]) {
121 dom
.id
= tmp_domains
.size();
122 dom
.domain
= DNSName(domain
["domain"].as
<string
>());
123 dom
.ttl
= domain
["ttl"].as
<int>();
125 for(YAML::const_iterator recs
= domain
["records"].begin(); recs
!= domain
["records"].end(); recs
++) {
126 DNSName qname
= DNSName(recs
->first
.as
<string
>());
127 vector
<GeoIPDNSResourceRecord
> rrs
;
129 for(YAML::Node item
: recs
->second
) {
130 YAML::const_iterator rec
= item
.begin();
131 GeoIPDNSResourceRecord rr
;
132 rr
.domain_id
= dom
.id
;
135 if (rec
->first
.IsNull()) {
138 string qtype
= boost::to_upper_copy(rec
->first
.as
<string
>());
141 rr
.has_weight
= false;
143 if (rec
->second
.IsNull()) {
145 } else if (rec
->second
.IsMap()) {
146 for(YAML::const_iterator iter
= rec
->second
.begin(); iter
!= rec
->second
.end(); iter
++) {
147 string attr
= iter
->first
.as
<string
>();
148 if (attr
== "content") {
149 string content
= iter
->second
.as
<string
>();
150 rr
.content
= content
;
151 } else if (attr
== "weight") {
152 rr
.weight
= iter
->second
.as
<int>();
153 if (rr
.weight
<= 0) {
154 g_log
<<Logger::Error
<<"Weight must be positive for " << rr
.qname
<< endl
;
155 throw PDNSException(string("Weight must be positive for ") + rr
.qname
.toLogString());
157 rr
.has_weight
= true;
158 } else if (attr
== "ttl") {
159 rr
.ttl
= iter
->second
.as
<int>();
161 g_log
<<Logger::Error
<<"Unsupported record attribute " << attr
<< " for " << rr
.qname
<< endl
;
162 throw PDNSException(string("Unsupported record attribute ") + attr
+ string(" for ") + rr
.qname
.toLogString());
166 string content
=rec
->second
.as
<string
>();
167 rr
.content
= content
;
173 std::swap(dom
.records
[qname
], rrs
);
176 for(YAML::const_iterator service
= domain
["services"].begin(); service
!= domain
["services"].end(); service
++) {
177 unsigned int netmask4
= 0, netmask6
= 0;
178 DNSName srvName
{service
->first
.as
<string
>()};
179 NetmaskTree
<vector
<string
> > nmt
;
181 // if it's an another map, we need to iterate it again, otherwise we just add two root entries.
182 if (service
->second
.IsMap()) {
183 for(YAML::const_iterator net
= service
->second
.begin(); net
!= service
->second
.end(); net
++) {
184 vector
<string
> value
;
185 if (net
->second
.IsSequence()) {
186 value
= net
->second
.as
<vector
<string
> >();
188 value
.push_back(net
->second
.as
<string
>());
190 if (net
->first
.as
<string
>() == "default") {
191 nmt
.insert(Netmask("0.0.0.0/0")).second
.assign(value
.begin(),value
.end());
192 nmt
.insert(Netmask("::/0")).second
.swap(value
);
194 Netmask nm
{net
->first
.as
<string
>()};
195 nmt
.insert(nm
).second
.swap(value
);
196 if (nm
.isIPv6() == true && netmask6
< nm
.getBits())
197 netmask6
= nm
.getBits();
198 if (nm
.isIPv6() == false && netmask4
< nm
.getBits())
199 netmask4
= nm
.getBits();
203 vector
<string
> value
;
204 if (service
->second
.IsSequence()) {
205 value
= service
->second
.as
<vector
<string
> >();
207 value
.push_back(service
->second
.as
<string
>());
209 nmt
.insert(Netmask("0.0.0.0/0")).second
.assign(value
.begin(),value
.end());
210 nmt
.insert(Netmask("::/0")).second
.swap(value
);
213 dom
.services
[srvName
].netmask4
= netmask4
;
214 dom
.services
[srvName
].netmask6
= netmask6
;
215 dom
.services
[srvName
].masks
.swap(nmt
);
218 // rectify the zone, first static records
219 for(auto &item
: dom
.records
) {
220 // ensure we have parent in records
221 DNSName name
= item
.first
;
222 while(name
.chopOff() && name
.isPartOf(dom
.domain
)) {
223 if (dom
.records
.find(name
) == dom
.records
.end() && !dom
.services
.count(name
)) { // don't ENT out a service!
224 GeoIPDNSResourceRecord rr
;
225 vector
<GeoIPDNSResourceRecord
> rrs
;
226 rr
.domain_id
= dom
.id
;
229 rr
.qtype
= QType(0); // empty non terminal
233 rr
.has_weight
= false;
235 std::swap(dom
.records
[name
], rrs
);
241 for(auto &item
: dom
.services
) {
242 // ensure we have parent in records
243 DNSName name
= item
.first
;
244 while(name
.chopOff() && name
.isPartOf(dom
.domain
)) {
245 if (dom
.records
.find(name
) == dom
.records
.end()) {
246 GeoIPDNSResourceRecord rr
;
247 vector
<GeoIPDNSResourceRecord
> rrs
;
248 rr
.domain_id
= dom
.id
;
255 rr
.has_weight
= false;
257 std::swap(dom
.records
[name
], rrs
);
262 // finally fix weights
263 for(auto &item
: dom
.records
) {
264 map
<uint16_t, float> weights
;
265 map
<uint16_t, float> sums
;
266 map
<uint16_t, GeoIPDNSResourceRecord
> lasts
;
267 bool has_weight
=false;
268 // first we look for used weight
269 for(const auto &rr
: item
.second
) {
270 weights
[rr
.qtype
.getCode()] += rr
.weight
;
271 if (rr
.has_weight
) has_weight
= true;
274 // put them back as probabilities and values..
275 for(auto &rr
: item
.second
) {
276 uint16_t rr_type
= rr
.qtype
.getCode();
277 rr
.weight
=static_cast<int>((static_cast<float>(rr
.weight
) / weights
[rr_type
])*1000.0);
278 sums
[rr_type
] += rr
.weight
;
279 rr
.has_weight
= has_weight
;
282 // remove rounding gap
283 for(auto &x
: lasts
) {
284 float sum
= sums
[x
.first
];
286 x
.second
.weight
+= (1000-sum
);
291 tmp_domains
.push_back(std::move(dom
));
295 std::swap(s_domains
, tmp_domains
);
297 extern std::function
<std::string(const std::string
& ip
, int)> g_getGeo
;
298 g_getGeo
= getGeoForLua
;
301 GeoIPBackend::~GeoIPBackend() {
303 WriteLock
wl(&s_state_lock
);
305 if (s_rc
== 0) { // last instance gets to cleanup
306 s_geoip_files
.clear();
314 bool GeoIPBackend::lookup_static(const GeoIPDomain
&dom
, const DNSName
&search
, const QType
&qtype
, const DNSName
& qdomain
, const Netmask
& addr
, GeoIPNetmask
&gl
) {
315 const auto& i
= dom
.records
.find(search
);
316 map
<uint16_t,int> cumul_probabilities
;
317 int probability_rnd
= 1+(dns_random(1000)); // setting probability=0 means it never is used
319 if (i
!= dom
.records
.end()) { // return static value
320 for(const auto& rr
: i
->second
) {
321 if (qtype
!= QType::ANY
&& rr
.qtype
!= qtype
) continue;
324 gl
.netmask
= (addr
.isIPv6()?128:32);
325 int comp
= cumul_probabilities
[rr
.qtype
.getCode()];
326 cumul_probabilities
[rr
.qtype
.getCode()] += rr
.weight
;
327 if (rr
.weight
== 0 || probability_rnd
< comp
|| probability_rnd
> (comp
+ rr
.weight
))
330 const string
& content
= format2str(rr
.content
, addr
, gl
);
331 if (rr
.qtype
!= QType::ENT
&& rr
.qtype
!= QType::TXT
&& content
.empty()) continue;
332 d_result
.push_back(rr
);
333 d_result
.back().content
= content
;
334 d_result
.back().qname
= qdomain
;
336 // ensure we get most strict netmask
337 for(DNSResourceRecord
& rr
: d_result
) {
338 rr
.scopeMask
= gl
.netmask
;
340 return true; // no need to go further
346 void GeoIPBackend::lookup(const QType
&qtype
, const DNSName
& qdomain
, int zoneId
, DNSPacket
*pkt_p
) {
347 ReadLock
rl(&s_state_lock
);
348 const GeoIPDomain
* dom
;
352 if (d_result
.size()>0)
353 throw PDNSException("Cannot perform lookup while another is running");
357 if (zoneId
> -1 && zoneId
< static_cast<int>(s_domains
.size()))
358 dom
= &(s_domains
[zoneId
]);
360 for(const GeoIPDomain
& i
: s_domains
) { // this is arguably wrong, we should probably find the most specific match
361 if (qdomain
.isPartOf(i
.domain
)) {
367 if (!found
) return; // not found
370 Netmask addr
{"0.0.0.0/0"};
372 addr
= Netmask(pkt_p
->getRealRemote());
376 (void)this->lookup_static(*dom
, qdomain
, qtype
, qdomain
, addr
, gl
);
378 const auto& target
= (*dom
).services
.find(qdomain
);
379 if (target
== (*dom
).services
.end()) return; // no hit
381 const NetmaskTree
<vector
<string
> >::node_type
* node
= target
->second
.masks
.lookup(addr
);
382 if (node
== NULL
) return; // no hit, again.
385 gl
.netmask
= node
->first
.getBits();
386 // figure out smallest sensible netmask
387 if (gl
.netmask
== 0) {
390 // get netmask from geoip backend
391 if (queryGeoIP(addr
, GeoIPInterface::Name
, tmp_gl
) == "unknown") {
393 gl
.netmask
= target
->second
.netmask6
;
395 gl
.netmask
= target
->second
.netmask4
;
399 gl
.netmask
= target
->second
.netmask6
;
401 gl
.netmask
= target
->second
.netmask4
;
404 // note that this means the array format won't work with indirect
405 for(auto it
= node
->second
.begin(); it
!= node
->second
.end(); it
++) {
406 sformat
= DNSName(format2str(*it
, addr
, gl
));
408 // see if the record can be found
409 if (this->lookup_static((*dom
), sformat
, qtype
, qdomain
, addr
, gl
))
413 if (!d_result
.empty()) {
414 g_log
<<Logger::Error
<<
415 "Cannot have static record and CNAME at the same time." <<
416 "Please fix your configuration for \"" << qdomain
<< "\", so that " <<
417 "it can be resolved by GeoIP backend directly."<< std::endl
;
422 // we need this line since we otherwise claim to have NS records etc
423 if (!(qtype
== QType::ANY
|| qtype
== QType::CNAME
)) return;
425 DNSResourceRecord rr
;
426 rr
.domain_id
= dom
->id
;
427 rr
.qtype
= QType::CNAME
;
429 rr
.content
= sformat
.toString();
432 rr
.scopeMask
= gl
.netmask
;
433 d_result
.push_back(rr
);
436 bool GeoIPBackend::get(DNSResourceRecord
&r
) {
437 if (d_result
.empty()) return false;
445 static string
queryGeoIP(const Netmask
& addr
, GeoIPInterface::GeoIPQueryAttribute attribute
, GeoIPNetmask
& gl
) {
446 string ret
= "unknown";
448 for(auto const& gi
: s_geoip_files
) {
450 const string ip
= addr
.toStringNoMask();
454 case GeoIPInterface::ASn
:
455 if (addr
.isIPv6()) found
= gi
->queryASnumV6(val
, gl
, ip
);
456 else found
=gi
->queryASnum(val
, gl
, ip
);
458 case GeoIPInterface::Name
:
459 if (addr
.isIPv6()) found
= gi
->queryNameV6(val
, gl
, ip
);
460 else found
= gi
->queryName(val
, gl
, ip
);
462 case GeoIPInterface::Continent
:
463 if (addr
.isIPv6()) found
= gi
->queryContinentV6(val
, gl
, ip
);
464 else found
= gi
->queryContinent(val
, gl
, ip
);
466 case GeoIPInterface::Region
:
467 if (addr
.isIPv6()) found
= gi
->queryRegionV6(val
, gl
, ip
);
468 else found
= gi
->queryRegion(val
, gl
, ip
);
470 case GeoIPInterface::Country
:
471 if (addr
.isIPv6()) found
= gi
->queryCountryV6(val
, gl
, ip
);
472 else found
= gi
->queryCountry(val
, gl
, ip
);
474 case GeoIPInterface::Country2
:
475 if (addr
.isIPv6()) found
= gi
->queryCountry2V6(val
, gl
, ip
);
476 else found
= gi
->queryCountry2(val
, gl
, ip
);
478 case GeoIPInterface::City
:
479 if (addr
.isIPv6()) found
= gi
->queryCityV6(val
, gl
, ip
);
480 else found
= gi
->queryCity(val
, gl
, ip
);
482 case GeoIPInterface::Location
:
484 boost::optional
<int> alt
, prec
;
485 if (addr
.isIPv6()) found
= gi
->queryLocationV6(gl
, ip
, lat
, lon
, alt
, prec
);
486 else found
= gi
->queryLocation(gl
, ip
, lat
, lon
, alt
, prec
);
487 val
= std::to_string(lat
)+" "+std::to_string(lon
);
491 if (!found
|| val
.empty() || val
== "--") continue; // try next database
493 std::transform(ret
.begin(), ret
.end(), ret
.begin(), ::tolower
);
497 if (ret
== "unknown") gl
.netmask
= (addr
.isIPv6()?128:32); // prevent caching
501 string
getGeoForLua(const std::string
& ip
, int qaint
)
503 GeoIPInterface::GeoIPQueryAttribute
qa((GeoIPInterface::GeoIPQueryAttribute
)qaint
);
505 const Netmask addr
{ip
};
507 string res
=queryGeoIP(addr
, qa
, gl
);
508 // cout<<"Result for "<<ip<<" lookup: "<<res<<endl;
509 if(qa
==GeoIPInterface::ASn
&& boost::starts_with(res
, "as"))
510 return res
.substr(2);
513 catch(std::exception
& e
) {
514 cout
<<"Error: "<<e
.what()<<endl
;
516 catch(PDNSException
& e
) {
517 cout
<<"Error: "<<e
.reason
<<endl
;
522 bool queryGeoLocation(const Netmask
& addr
, GeoIPNetmask
& gl
, double& lat
, double& lon
,
523 boost::optional
<int>& alt
, boost::optional
<int>& prec
)
525 for(auto const& gi
: s_geoip_files
) {
528 if (gi
->queryLocationV6(gl
, addr
.toStringNoMask(), lat
, lon
, alt
, prec
))
530 } else if (gi
->queryLocation(gl
, addr
.toStringNoMask(), lat
, lon
, alt
, prec
))
536 string
GeoIPBackend::format2str(string sformat
, const Netmask
& addr
, GeoIPNetmask
& gl
) {
537 string::size_type cur
,last
;
538 boost::optional
<int> alt
, prec
;
540 time_t t
= time((time_t*)NULL
);
541 GeoIPNetmask tmp_gl
; // largest wins
546 while((cur
= sformat
.find("%", last
)) != string::npos
) {
550 if (!sformat
.compare(cur
,3,"%cn")) {
551 rep
= queryGeoIP(addr
, GeoIPInterface::Continent
, tmp_gl
);
552 } else if (!sformat
.compare(cur
,3,"%co")) {
553 rep
= queryGeoIP(addr
, GeoIPInterface::Country
, tmp_gl
);
554 } else if (!sformat
.compare(cur
,3,"%cc")) {
555 rep
= queryGeoIP(addr
, GeoIPInterface::Country2
, tmp_gl
);
556 } else if (!sformat
.compare(cur
,3,"%af")) {
557 rep
= (addr
.isIPv6()?"v6":"v4");
558 } else if (!sformat
.compare(cur
,3,"%as")) {
559 rep
= queryGeoIP(addr
, GeoIPInterface::ASn
, tmp_gl
);
560 } else if (!sformat
.compare(cur
,3,"%re")) {
561 rep
= queryGeoIP(addr
, GeoIPInterface::Region
, tmp_gl
);
562 } else if (!sformat
.compare(cur
,3,"%na")) {
563 rep
= queryGeoIP(addr
, GeoIPInterface::Name
, tmp_gl
);
564 } else if (!sformat
.compare(cur
,3,"%ci")) {
565 rep
= queryGeoIP(addr
, GeoIPInterface::City
, tmp_gl
);
566 } else if (!sformat
.compare(cur
,4,"%loc")) {
570 if (!queryGeoLocation(addr
, gl
, lat
, lon
, alt
, prec
)) {
573 ns
= (lat
>0) ? 'N' : 'S';
574 ew
= (lon
>0) ? 'E' : 'W';
578 d1
= static_cast<int>(lat
);
579 d2
= static_cast<int>(lon
);
580 m1
= static_cast<int>((lat
- d1
)*60.0);
581 m2
= static_cast<int>((lon
- d2
)*60.0);
582 s1
= static_cast<double>(lat
- d1
- m1
/60.0)*3600.0;
583 s2
= static_cast<double>(lon
- d2
- m2
/60.0)*3600.0;
584 rep
= str(boost::format("%d %d %0.3f %c %d %d %0.3f %c") %
585 d1
% m1
% s1
% ns
% d2
% m2
% s2
% ew
);
587 rep
= rep
+ str(boost::format(" %d.00") % *alt
);
589 rep
= rep
+ string(" 0.00");
591 rep
= rep
+ str(boost::format(" %dm") % *prec
);
594 } else if (!sformat
.compare(cur
,4,"%lat")) {
595 if (!queryGeoLocation(addr
, gl
, lat
, lon
, alt
, prec
)) {
598 rep
= str(boost::format("%lf") % lat
);
601 } else if (!sformat
.compare(cur
,4,"%lon")) {
602 if (!queryGeoLocation(addr
, gl
, lat
, lon
, alt
, prec
)) {
605 rep
= str(boost::format("%lf") % lon
);
608 } else if (!sformat
.compare(cur
,3,"%hh")) {
609 rep
= boost::str(boost::format("%02d") % gtm
.tm_hour
);
610 tmp_gl
.netmask
= (addr
.isIPv6()?128:32);
611 } else if (!sformat
.compare(cur
,3,"%yy")) {
612 rep
= boost::str(boost::format("%02d") % (gtm
.tm_year
+ 1900));
613 tmp_gl
.netmask
= (addr
.isIPv6()?128:32);
614 } else if (!sformat
.compare(cur
,3,"%dd")) {
615 rep
= boost::str(boost::format("%02d") % (gtm
.tm_yday
+ 1));
616 tmp_gl
.netmask
= (addr
.isIPv6()?128:32);
617 } else if (!sformat
.compare(cur
,4,"%wds")) {
619 rep
= GeoIP_WEEKDAYS
[gtm
.tm_wday
];
620 tmp_gl
.netmask
= (addr
.isIPv6()?128:32);
621 } else if (!sformat
.compare(cur
,4,"%mos")) {
623 rep
= GeoIP_MONTHS
[gtm
.tm_mon
];
624 tmp_gl
.netmask
= (addr
.isIPv6()?128:32);
625 } else if (!sformat
.compare(cur
,3,"%wd")) {
626 rep
= boost::str(boost::format("%02d") % (gtm
.tm_wday
+ 1));
627 tmp_gl
.netmask
= (addr
.isIPv6()?128:32);
628 } else if (!sformat
.compare(cur
,3,"%mo")) {
629 rep
= boost::str(boost::format("%02d") % (gtm
.tm_mon
+ 1));
630 tmp_gl
.netmask
= (addr
.isIPv6()?128:32);
631 } else if (!sformat
.compare(cur
,4,"%ip6")) {
634 rep
= addr
.toStringNoMask();
637 tmp_gl
.netmask
= (addr
.isIPv6()?128:32);
638 } else if (!sformat
.compare(cur
,4,"%ip4")) {
641 rep
= addr
.toStringNoMask();
644 tmp_gl
.netmask
= (addr
.isIPv6()?128:32);
645 } else if (!sformat
.compare(cur
,3,"%ip")) {
646 rep
= addr
.toStringNoMask();
647 tmp_gl
.netmask
= (addr
.isIPv6()?128:32);
648 } else if (!sformat
.compare(cur
,2,"%%")) {
649 last
= cur
+ 2; continue;
651 last
= cur
+ 1; continue;
653 if (tmp_gl
.netmask
> gl
.netmask
) gl
.netmask
= tmp_gl
.netmask
;
654 sformat
.replace(cur
, nrep
, rep
);
655 last
= cur
+ rep
.size(); // move to next attribute
660 void GeoIPBackend::reload() {
661 WriteLock
wl(&s_state_lock
);
665 } catch (PDNSException
&pex
) {
666 g_log
<<Logger::Error
<<"GeoIP backend reload failed: " << pex
.reason
<< endl
;
667 } catch (std::exception
&stex
) {
668 g_log
<<Logger::Error
<<"GeoIP backend reload failed: " << stex
.what() << endl
;
670 g_log
<<Logger::Error
<<"GeoIP backend reload failed" << endl
;
674 void GeoIPBackend::rediscover(string
* status
) {
678 bool GeoIPBackend::getDomainInfo(const DNSName
& domain
, DomainInfo
&di
, bool getSerial
) {
679 ReadLock
rl(&s_state_lock
);
681 for(GeoIPDomain dom
: s_domains
) {
682 if (dom
.domain
== domain
) {
684 this->getSOA(domain
, sd
);
686 di
.zone
= dom
.domain
;
687 di
.serial
= sd
.serial
;
688 di
.kind
= DomainInfo::Native
;
696 bool GeoIPBackend::getAllDomainMetadata(const DNSName
& name
, std::map
<std::string
, std::vector
<std::string
> >& meta
) {
697 if (!d_dnssec
) return false;
699 ReadLock
rl(&s_state_lock
);
700 for(GeoIPDomain dom
: s_domains
) {
701 if (dom
.domain
== name
) {
702 if (hasDNSSECkey(dom
.domain
)) {
703 meta
[string("NSEC3NARROW")].push_back("1");
704 meta
[string("NSEC3PARAM")].push_back("1 0 1 f95a");
712 bool GeoIPBackend::getDomainMetadata(const DNSName
& name
, const std::string
& kind
, std::vector
<std::string
>& meta
) {
713 if (!d_dnssec
) return false;
715 ReadLock
rl(&s_state_lock
);
716 for(GeoIPDomain dom
: s_domains
) {
717 if (dom
.domain
== name
) {
718 if (hasDNSSECkey(dom
.domain
)) {
719 if (kind
== "NSEC3NARROW")
720 meta
.push_back(string("1"));
721 if (kind
== "NSEC3PARAM")
722 meta
.push_back(string("1 0 1 f95a"));
730 bool GeoIPBackend::getDomainKeys(const DNSName
& name
, std::vector
<DNSBackend::KeyData
>& keys
) {
731 if (!d_dnssec
) return false;
732 ReadLock
rl(&s_state_lock
);
733 for(GeoIPDomain dom
: s_domains
) {
734 if (dom
.domain
== name
) {
737 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
|REG_EXTENDED
);
738 ostringstream pathname
;
739 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
741 if (glob(pathname
.str().c_str(),GLOB_ERR
,NULL
,&glob_result
) == 0) {
742 for(size_t i
=0;i
<glob_result
.gl_pathc
;i
++) {
743 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
744 DNSBackend::KeyData kd
;
745 kd
.id
= pdns_stou(glob_result
.gl_pathv
[i
]+regm
[3].rm_so
);
746 kd
.active
= !strncmp(glob_result
.gl_pathv
[i
]+regm
[4].rm_so
, "1", 1);
748 kd
.flags
= pdns_stou(glob_result
.gl_pathv
[i
]+regm
[2].rm_so
);
749 ifstream
ifs(glob_result
.gl_pathv
[i
]);
750 ostringstream content
;
753 ifs
.read(buffer
, sizeof buffer
);
754 if (ifs
.gcount()>0) {
755 content
<< string(buffer
, ifs
.gcount());
759 kd
.content
= content
.str();
765 globfree(&glob_result
);
772 bool GeoIPBackend::removeDomainKey(const DNSName
& name
, unsigned int id
) {
773 if (!d_dnssec
) return false;
774 WriteLock
rl(&s_state_lock
);
777 for(GeoIPDomain dom
: s_domains
) {
778 if (dom
.domain
== name
) {
781 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
|REG_EXTENDED
);
782 ostringstream pathname
;
783 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
785 if (glob(pathname
.str().c_str(),GLOB_ERR
,NULL
,&glob_result
) == 0) {
786 for(size_t i
=0;i
<glob_result
.gl_pathc
;i
++) {
787 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
788 unsigned int kid
= pdns_stou(glob_result
.gl_pathv
[i
]+regm
[3].rm_so
);
790 if (unlink(glob_result
.gl_pathv
[i
])) {
791 cerr
<< "Cannot delete key:" << strerror(errno
) << endl
;
799 globfree(&glob_result
);
806 bool GeoIPBackend::addDomainKey(const DNSName
& name
, const KeyData
& key
, int64_t& id
) {
807 if (!d_dnssec
) return false;
808 WriteLock
rl(&s_state_lock
);
809 unsigned int nextid
=1;
811 for(GeoIPDomain dom
: s_domains
) {
812 if (dom
.domain
== name
) {
815 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
|REG_EXTENDED
);
816 ostringstream pathname
;
817 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
819 if (glob(pathname
.str().c_str(),GLOB_ERR
,NULL
,&glob_result
) == 0) {
820 for(size_t i
=0;i
<glob_result
.gl_pathc
;i
++) {
821 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
822 unsigned int kid
= pdns_stou(glob_result
.gl_pathv
[i
]+regm
[3].rm_so
);
823 if (kid
>= nextid
) nextid
= kid
+1;
828 globfree(&glob_result
);
830 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "." << key
.flags
<< "." << nextid
<< "." << (key
.active
?"1":"0") << ".key";
831 ofstream
ofs(pathname
.str().c_str());
832 ofs
.write(key
.content
.c_str(), key
.content
.size());
842 bool GeoIPBackend::activateDomainKey(const DNSName
& name
, unsigned int id
) {
843 if (!d_dnssec
) return false;
844 WriteLock
rl(&s_state_lock
);
845 for(GeoIPDomain dom
: s_domains
) {
846 if (dom
.domain
== name
) {
849 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
|REG_EXTENDED
);
850 ostringstream pathname
;
851 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
853 if (glob(pathname
.str().c_str(),GLOB_ERR
,NULL
,&glob_result
) == 0) {
854 for(size_t i
=0;i
<glob_result
.gl_pathc
;i
++) {
855 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
856 unsigned int kid
= pdns_stou(glob_result
.gl_pathv
[i
]+regm
[3].rm_so
);
857 if (kid
== id
&& !strcmp(glob_result
.gl_pathv
[i
]+regm
[4].rm_so
,"0")) {
858 ostringstream newpath
;
859 newpath
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "." << pdns_stou(glob_result
.gl_pathv
[i
]+regm
[2].rm_so
) << "." << kid
<< ".1.key";
860 if (rename(glob_result
.gl_pathv
[i
], newpath
.str().c_str())) {
861 cerr
<< "Cannot activate key: " << strerror(errno
) << endl
;
867 globfree(&glob_result
);
875 bool GeoIPBackend::deactivateDomainKey(const DNSName
& name
, unsigned int id
) {
876 if (!d_dnssec
) return false;
877 WriteLock
rl(&s_state_lock
);
878 for(GeoIPDomain dom
: s_domains
) {
879 if (dom
.domain
== name
) {
882 regcomp(®
, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE
|REG_EXTENDED
);
883 ostringstream pathname
;
884 pathname
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "*.key";
886 if (glob(pathname
.str().c_str(),GLOB_ERR
,NULL
,&glob_result
) == 0) {
887 for(size_t i
=0;i
<glob_result
.gl_pathc
;i
++) {
888 if (regexec(®
, glob_result
.gl_pathv
[i
], 5, regm
, 0) == 0) {
889 unsigned int kid
= pdns_stou(glob_result
.gl_pathv
[i
]+regm
[3].rm_so
);
890 if (kid
== id
&& !strcmp(glob_result
.gl_pathv
[i
]+regm
[4].rm_so
,"1")) {
891 ostringstream newpath
;
892 newpath
<< getArg("dnssec-keydir") << "/" << dom
.domain
.toStringNoDot() << "." << pdns_stou(glob_result
.gl_pathv
[i
]+regm
[2].rm_so
) << "." << kid
<< ".0.key";
893 if (rename(glob_result
.gl_pathv
[i
], newpath
.str().c_str())) {
894 cerr
<< "Cannot deactivate key: " << strerror(errno
) << endl
;
900 globfree(&glob_result
);
908 bool GeoIPBackend::publishDomainKey(const DNSName
& name
, unsigned int id
) {
912 bool GeoIPBackend::unpublishDomainKey(const DNSName
& name
, unsigned int id
) {
917 bool GeoIPBackend::hasDNSSECkey(const DNSName
& name
) {
918 ostringstream pathname
;
919 pathname
<< getArg("dnssec-keydir") << "/" << name
.toStringNoDot() << "*.key";
921 if (glob(pathname
.str().c_str(),GLOB_ERR
,NULL
,&glob_result
) == 0) {
922 globfree(&glob_result
);
928 class GeoIPFactory
: public BackendFactory
{
930 GeoIPFactory() : BackendFactory("geoip") {}
932 void declareArguments(const string
&suffix
= "") {
933 declare(suffix
, "zones-file", "YAML file to load zone(s) configuration", "");
934 declare(suffix
, "database-files", "File(s) to load geoip data from ([driver:]path[;opt=value]", "");
935 declare(suffix
, "dnssec-keydir", "Directory to hold dnssec keys (also turns DNSSEC on)", "");
938 DNSBackend
*make(const string
&suffix
) {
939 return new GeoIPBackend(suffix
);
946 BackendMakers().report(new GeoIPFactory
);
947 g_log
<< Logger::Info
<< "[geoipbackend] This is the geoip backend version " VERSION
949 << " (" __DATE__
" " __TIME__
")"
951 << " reporting" << endl
;
955 static GeoIPLoader geoiploader
;