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.
27 #include "dnssecinfra.hh"
28 #include "dnsseckeeper.hh"
31 #include "communicator.hh"
33 #include <boost/utility.hpp>
34 #include "dnsbackend.hh"
35 #include "ueberbackend.hh"
36 #include "packethandler.hh"
37 #include "resolver.hh"
40 #include "arguments.hh"
41 #include "auth-caches.hh"
44 #include "inflighter.cc"
45 #include "namespaces.hh"
46 #include "common_startup.hh"
50 void CommunicatorClass::addSuckRequest(const DNSName
&domain
, const ComboAddress
& master
)
56 pair
<UniQueue::iterator
, bool> res
;
58 res
=d_suckdomains
.push_back(sr
);
67 bool isDnssecZone
{false};
68 bool isPresigned
{false};
70 bool optOutFlag
{false};
71 NSEC3PARAMRecordContent ns3pr
;
74 unsigned int soa_serial
{0};
75 set
<DNSName
> nsset
, qnames
, secured
;
81 void CommunicatorClass::ixfrSuck(const DNSName
&domain
, const TSIGTriplet
& tt
, const ComboAddress
& laddr
, const ComboAddress
& remote
, unique_ptr
<AuthLua4
>& pdl
,
82 ZoneStatus
& zs
, vector
<DNSRecord
>* axfr
)
84 UeberBackend B
; // fresh UeberBackend
88 // bool transaction=false;
90 DNSSECKeeper
dk (&B
); // reuse our UeberBackend copy for DNSSECKeeper
92 bool wrongDomainKind
= false;
93 // this checks three error conditions, and sets wrongDomainKind if we hit the third & had an error
94 if(!B
.getDomainInfo(domain
, di
) || !di
.backend
|| (wrongDomainKind
= true, di
.kind
!= DomainInfo::Slave
)) { // di.backend and B are mostly identical
96 g_log
<<Logger::Error
<<"Can't determine backend for domain '"<<domain
<<"', not configured as slave"<<endl
;
98 g_log
<<Logger::Error
<<"Can't determine backend for domain '"<<domain
<<"'"<<endl
;
103 memset(&st
, 0, sizeof(st
));
107 drsoa
.d_content
= std::make_shared
<SOARecordContent
>(g_rootdnsname
, g_rootdnsname
, st
);
108 auto deltas
= getIXFRDeltas(remote
, domain
, drsoa
, tt
, laddr
.sin4
.sin_family
? &laddr
: 0, ((size_t) ::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024);
109 zs
.numDeltas
=deltas
.size();
110 // cout<<"Got "<<deltas.size()<<" deltas from serial "<<di.serial<<", applying.."<<endl;
112 for(const auto& d
: deltas
) {
113 const auto& remove
= d
.first
;
114 const auto& add
= d
.second
;
115 // cout<<"Delta sizes: "<<remove.size()<<", "<<add.size()<<endl;
117 if(remove
.empty()) { // we got passed an AXFR!
123 // our hammer is 'replaceRRSet(domain_id, qname, qt, vector<DNSResourceRecord>& rrset)
124 // which thinks in terms of RRSETs
125 // however, IXFR does not, and removes and adds *records* (bummer)
126 // this means that we must group updates by {qname,qtype}, retrieve the RRSET, apply
127 // the add/remove updates, and replaceRRSet the whole thing.
130 map
<pair
<DNSName
,uint16_t>, pair
<vector
<DNSRecord
>, vector
<DNSRecord
> > > grouped
;
132 for(const auto& x
: remove
)
133 grouped
[{x
.d_name
, x
.d_type
}].first
.push_back(x
);
134 for(const auto& x
: add
)
135 grouped
[{x
.d_name
, x
.d_type
}].second
.push_back(x
);
137 di
.backend
->startTransaction(domain
, -1);
138 for(const auto g
: grouped
) {
139 vector
<DNSRecord
> rrset
;
142 B
.lookup(QType(g
.first
.second
), g
.first
.first
+domain
, di
.id
);
144 zrr
.dr
.d_name
.makeUsRelative(domain
);
145 rrset
.push_back(zrr
.dr
);
149 rrset
.erase(remove_if(rrset
.begin(), rrset
.end(),
150 [&g
](const DNSRecord
& dr
) {
151 return count(g
.second
.first
.cbegin(),
152 g
.second
.first
.cend(), dr
);
154 // the DNSRecord== operator compares on name, type, class and lowercase content representation
156 for(const auto& x
: g
.second
.second
) {
160 vector
<DNSResourceRecord
> replacement
;
161 for(const auto& dr
: rrset
) {
162 auto rr
= DNSResourceRecord::fromWire(dr
);
164 rr
.domain_id
= di
.id
;
165 if(dr
.d_type
== QType::SOA
) {
166 // cout<<"New SOA: "<<x.d_content->getZoneRepresentation()<<endl;
167 auto sr
= getRR
<SOARecordContent
>(dr
);
168 zs
.soa_serial
=sr
->d_st
.serial
;
171 replacement
.push_back(rr
);
174 di
.backend
->replaceRRSet(di
.id
, g
.first
.first
+domain
, QType(g
.first
.second
), replacement
);
176 di
.backend
->commitTransaction();
179 catch(std::exception
& p
) {
180 g_log
<<Logger::Error
<<"Got exception during IXFR: "<<p
.what()<<endl
;
183 catch(PDNSException
& p
) {
184 g_log
<<Logger::Error
<<"Got exception during IXFR: "<<p
.reason
<<endl
;
190 static bool processRecordForZS(const DNSName
& domain
, bool& firstNSEC3
, DNSResourceRecord
& rr
, ZoneStatus
& zs
)
192 switch(rr
.qtype
.getCode()) {
193 case QType::NSEC3PARAM
:
194 zs
.ns3pr
= NSEC3PARAMRecordContent(rr
.content
);
195 zs
.isDnssecZone
= zs
.isNSEC3
= true;
199 NSEC3RecordContent
ns3rc(rr
.content
);
201 zs
.isDnssecZone
= zs
.isPresigned
= true;
203 } else if (zs
.optOutFlag
!= (ns3rc
.d_flags
& 1))
204 throw PDNSException("Zones with a mixture of Opt-Out NSEC3 RRs and non-Opt-Out NSEC3 RRs are not supported.");
205 zs
.optOutFlag
= ns3rc
.d_flags
& 1;
206 if (ns3rc
.isSet(QType::NS
) && !(rr
.qname
==domain
)) {
207 DNSName hashPart
= rr
.qname
.makeRelative(domain
);
208 zs
.secured
.insert(hashPart
);
214 zs
.isDnssecZone
= zs
.isPresigned
= true;
219 zs
.nsset
.insert(rr
.qname
);
223 zs
.qnames
.insert(rr
.qname
);
225 rr
.domain_id
=zs
.domain_id
;
229 /* So this code does a number of things.
230 1) It will AXFR a domain from a master
231 The code can retrieve the current serial number in the database itself.
232 It may attempt an IXFR
233 2) It will filter the zone through a lua *filter* script
234 3) The code walks through the zone records do determine DNSSEC status (secured, nsec/nsec3, optout)
235 4) It inserts the zone into the database
236 With the right 'ordername' fields
237 5) It updates the Empty Non Terminals
240 static vector
<DNSResourceRecord
> doAxfr(const ComboAddress
& raddr
, const DNSName
& domain
, const TSIGTriplet
& tt
, const ComboAddress
& laddr
, unique_ptr
<AuthLua4
>& pdl
, ZoneStatus
& zs
)
242 uint16_t axfr_timeout
=::arg().asNum("axfr-fetch-timeout");
243 vector
<DNSResourceRecord
> rrs
;
244 AXFRRetriever
retriever(raddr
, domain
, tt
, (laddr
.sin4
.sin_family
== 0) ? NULL
: &laddr
, ((size_t) ::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024, axfr_timeout
);
245 Resolver::res_t recs
;
247 bool firstNSEC3
{true};
248 bool soa_received
{false};
249 while(retriever
.getChunk(recs
, nullptr, axfr_timeout
)) {
251 g_log
<<Logger::Error
<<"AXFR started for '"<<domain
<<"'"<<endl
;
255 for(Resolver::res_t::iterator i
=recs
.begin();i
!=recs
.end();++i
) {
256 i
->qname
.makeUsLowerCase();
257 if(i
->qtype
.getCode() == QType::OPT
|| i
->qtype
.getCode() == QType::TSIG
) // ignore EDNS0 & TSIG
260 if(!i
->qname
.isPartOf(domain
)) {
261 g_log
<<Logger::Error
<<"Remote "<<raddr
.toStringWithPort()<<" tried to sneak in out-of-zone data '"<<i
->qname
<<"'|"<<i
->qtype
.getName()<<" during AXFR of zone '"<<domain
<<"', ignoring"<<endl
;
265 vector
<DNSResourceRecord
> out
;
266 if(!pdl
|| !pdl
->axfrfilter(raddr
, domain
, *i
, out
)) {
267 out
.push_back(*i
); // if axfrfilter didn't do anything, we put our record in 'out' ourselves
270 for(DNSResourceRecord
& rr
: out
) {
271 if(!rr
.qname
.isPartOf(domain
)) {
272 g_log
<<Logger::Error
<<"Lua axfrfilter() filter tried to sneak in out-of-zone data '"<<i
->qname
<<"'|"<<i
->qtype
.getName()<<" during AXFR of zone '"<<domain
<<"', ignoring"<<endl
;
275 if(!processRecordForZS(domain
, firstNSEC3
, rr
, zs
))
277 if(rr
.qtype
.getCode() == QType::SOA
) {
279 continue; //skip the last SOA
281 fillSOAData(rr
.content
,sd
);
282 zs
.soa_serial
= sd
.serial
;
295 void CommunicatorClass::suck(const DNSName
&domain
, const ComboAddress
& remote
)
299 if(d_inprogress
.count(domain
)) {
302 d_inprogress
.insert(domain
);
304 RemoveSentinel
rs(domain
, this); // this removes us from d_inprogress when we go out of scope
306 g_log
<<Logger::Error
<<"Initiating transfer of '"<<domain
<<"' from remote '"<<remote
<<"'"<<endl
;
307 UeberBackend B
; // fresh UeberBackend
311 bool transaction
=false;
313 DNSSECKeeper
dk (&B
); // reuse our UeberBackend copy for DNSSECKeeper
314 bool wrongDomainKind
= false;
315 // this checks three error conditions & sets wrongDomainKind if we hit the third
316 if(!B
.getDomainInfo(domain
, di
) || !di
.backend
|| (wrongDomainKind
= true, di
.kind
!= DomainInfo::Slave
)) { // di.backend and B are mostly identical
318 g_log
<<Logger::Error
<<"Can't determine backend for domain '"<<domain
<<"', not configured as slave"<<endl
;
320 g_log
<<Logger::Error
<<"Can't determine backend for domain '"<<domain
<<"'"<<endl
;
327 if(dk
.getTSIGForAccess(domain
, remote
, &tt
.name
)) {
329 if(B
.getTSIGKey(tt
.name
, &tt
.algo
, &tsigsecret64
)) {
330 if(B64Decode(tsigsecret64
, tt
.secret
)) {
331 g_log
<<Logger::Error
<<"Unable to Base-64 decode TSIG key '"<<tt
.name
<<"' for domain '"<<domain
<<"' not found"<<endl
;
335 g_log
<<Logger::Error
<<"TSIG key '"<<tt
.name
<<"' for domain '"<<domain
<<"' not found"<<endl
;
341 unique_ptr
<AuthLua4
> pdl
{nullptr};
342 vector
<string
> scripts
;
343 string script
=::arg()["lua-axfr-script"];
344 if(B
.getDomainMetadata(domain
, "LUA-AXFR-SCRIPT", scripts
) && !scripts
.empty()) {
345 if (pdns_iequals(scripts
[0], "NONE")) {
353 pdl
= make_unique
<AuthLua4
>();
354 pdl
->loadFile(script
);
355 g_log
<<Logger::Info
<<"Loaded Lua script '"<<script
<<"' to edit the incoming AXFR of '"<<domain
<<"'"<<endl
;
357 catch(std::exception
& e
) {
358 g_log
<<Logger::Error
<<"Failed to load Lua editing script '"<<script
<<"' for incoming AXFR of '"<<domain
<<"': "<<e
.what()<<endl
;
363 vector
<string
> localaddr
;
366 if(B
.getDomainMetadata(domain
, "AXFR-SOURCE", localaddr
) && !localaddr
.empty()) {
368 laddr
= ComboAddress(localaddr
[0]);
369 g_log
<<Logger::Info
<<"AXFR source for domain '"<<domain
<<"' set to "<<localaddr
[0]<<endl
;
371 catch(std::exception
& e
) {
372 g_log
<<Logger::Error
<<"Failed to load AXFR source '"<<localaddr
[0]<<"' for incoming AXFR of '"<<domain
<<"': "<<e
.what()<<endl
;
376 if(remote
.sin4
.sin_family
== AF_INET
&& !::arg()["query-local-address"].empty()) {
377 laddr
= ComboAddress(::arg()["query-local-address"]);
378 } else if(remote
.sin4
.sin_family
== AF_INET6
&& !::arg()["query-local-address6"].empty()) {
379 laddr
= ComboAddress(::arg()["query-local-address6"]);
381 bool isv6
= remote
.sin4
.sin_family
== AF_INET6
;
382 g_log
<<Logger::Error
<<"Unable to AXFR, destination address is IPv" << (isv6
? "6" : "4") << ", but query-local-address"<< (isv6
? "6" : "") << " is unset!"<<endl
;
387 bool hadDnssecZone
= false;
388 bool hadPresigned
= false;
389 bool hadNSEC3
= false;
390 NSEC3PARAMRecordContent hadNs3pr
;
391 bool hadNarrow
=false;
394 vector
<DNSResourceRecord
> rrs
;
395 if(dk
.isSecuredZone(domain
)) {
397 hadPresigned
=dk
.isPresigned(domain
);
398 if (dk
.getNSEC3PARAM(domain
, &zs
.ns3pr
, &zs
.isNarrow
)) {
401 hadNarrow
= zs
.isNarrow
;
406 B
.getDomainMetadata(domain
, "IXFR", meta
);
407 if(!meta
.empty() && meta
[0]=="1") {
408 vector
<DNSRecord
> axfr
;
409 g_log
<<Logger::Warning
<<"Starting IXFR of '"<<domain
<<"' from remote "<<remote
<<endl
;
410 ixfrSuck(domain
, tt
, laddr
, remote
, pdl
, zs
, &axfr
);
412 g_log
<<Logger::Warning
<<"IXFR of '"<<domain
<<"' from remote '"<<remote
<<"' turned into an AXFR"<<endl
;
413 bool firstNSEC3
=true;
414 rrs
.reserve(axfr
.size());
415 for(const auto& dr
: axfr
) {
416 auto rr
= DNSResourceRecord::fromWire(dr
);
417 (rr
.qname
+= domain
).makeUsLowerCase();
418 rr
.domain_id
= zs
.domain_id
;
419 if(!processRecordForZS(domain
, firstNSEC3
, rr
, zs
))
421 if(dr
.d_type
== QType::SOA
) {
422 auto sd
= getRR
<SOARecordContent
>(dr
);
423 zs
.soa_serial
= sd
->d_st
.serial
;
429 g_log
<<Logger::Warning
<<"Done with IXFR of '"<<domain
<<"' from remote '"<<remote
<<"', got "<<zs
.numDeltas
<<" delta"<<addS(zs
.numDeltas
)<<", serial now "<<zs
.soa_serial
<<endl
;
430 purgeAuthCaches(domain
.toString()+"$");
437 g_log
<<Logger::Warning
<<"Starting AXFR of '"<<domain
<<"' from remote "<<remote
<<endl
;
438 rrs
= doAxfr(remote
, domain
, tt
, laddr
, pdl
, zs
);
439 g_log
<<Logger::Warning
<<"AXFR of '"<<domain
<<"' from remote "<<remote
<<" done"<<endl
;
443 zs
.ns3pr
.d_flags
= zs
.optOutFlag
? 1 : 0;
446 if(!zs
.isPresigned
) {
447 DNSSECKeeper::keyset_t keys
= dk
.getKeys(domain
);
449 zs
.isDnssecZone
= true;
450 zs
.isNSEC3
= hadNSEC3
;
452 zs
.optOutFlag
= (hadNs3pr
.d_flags
& 1);
453 zs
.isNarrow
= hadNarrow
;
457 if(zs
.isDnssecZone
) {
459 g_log
<<Logger::Info
<<"Adding NSEC ordering information"<<endl
;
460 else if(!zs
.isNarrow
)
461 g_log
<<Logger::Info
<<"Adding NSEC3 hashed ordering information for '"<<domain
<<"'"<<endl
;
463 g_log
<<Logger::Info
<<"Erasing NSEC3 ordering since we are narrow, only setting 'auth' fields"<<endl
;
467 transaction
=di
.backend
->startTransaction(domain
, zs
.domain_id
);
468 g_log
<<Logger::Error
<<"Backend transaction started for '"<<domain
<<"' storage"<<endl
;
470 // update the presigned flag and NSEC3PARAM
471 if (zs
.isDnssecZone
) {
472 // update presigned if there was a change
473 if (zs
.isPresigned
&& !hadPresigned
) {
474 // zone is now presigned
475 dk
.setPresigned(domain
);
476 } else if (hadPresigned
&& !zs
.isPresigned
) {
477 // zone is no longer presigned
478 dk
.unsetPresigned(domain
);
482 // zone is NSEC3, only update if there was a change
483 if (!hadNSEC3
|| (hadNarrow
!= zs
.isNarrow
) ||
484 (zs
.ns3pr
.d_algorithm
!= hadNs3pr
.d_algorithm
) ||
485 (zs
.ns3pr
.d_flags
!= hadNs3pr
.d_flags
) ||
486 (zs
.ns3pr
.d_iterations
!= hadNs3pr
.d_iterations
) ||
487 (zs
.ns3pr
.d_salt
!= hadNs3pr
.d_salt
)) {
488 dk
.setNSEC3PARAM(domain
, zs
.ns3pr
, zs
.isNarrow
);
490 } else if (hadNSEC3
) {
491 // zone is no longer NSEC3
492 dk
.unsetNSEC3PARAM(domain
);
494 } else if (hadDnssecZone
) {
495 // zone is no longer signed
498 dk
.unsetPresigned(domain
);
502 dk
.unsetNSEC3PARAM(domain
);
507 uint32_t maxent
= ::arg().asNum("max-ent-entries");
508 DNSName shorter
, ordername
;
510 map
<DNSName
,bool> nonterm
;
513 for(DNSResourceRecord
& rr
: rrs
) {
514 if(!zs
.isPresigned
) {
515 if (rr
.qtype
.getCode() == QType::RRSIG
)
517 if(zs
.isDnssecZone
&& rr
.qtype
.getCode() == QType::DNSKEY
&& !::arg().mustDo("direct-dnskey"))
521 // Figure out auth and ents
527 if (!zs
.qnames
.count(shorter
))
528 rrterm
.insert(shorter
);
530 if(zs
.nsset
.count(shorter
) && rr
.qtype
.getCode() != QType::DS
)
533 if (shorter
==domain
) // stop at apex
535 }while(shorter
.chopOff());
538 if(doent
&& !rrterm
.empty()) {
540 if (!rr
.auth
&& rr
.qtype
.getCode() == QType::NS
) {
542 ordername
=DNSName(toBase32Hex(hashQNameWithSalt(zs
.ns3pr
, rr
.qname
)));
543 auth
=(!zs
.isNSEC3
|| !zs
.optOutFlag
|| zs
.secured
.count(ordername
));
547 for(const auto &nt
: rrterm
){
548 if (!nonterm
.count(nt
))
549 nonterm
.insert(pair
<DNSName
, bool>(nt
, auth
));
554 if(nonterm
.size() > maxent
) {
555 g_log
<<Logger::Error
<<"AXFR zone "<<domain
<<" has too many empty non terminals."<<endl
;
561 // RRSIG is always auth, even inside a delegation
562 if (rr
.qtype
.getCode() == QType::RRSIG
)
565 // Add ordername and insert record
566 if (zs
.isDnssecZone
&& rr
.qtype
.getCode() != QType::RRSIG
) {
569 ordername
=DNSName(toBase32Hex(hashQNameWithSalt(zs
.ns3pr
, rr
.qname
)));
570 if(!zs
.isNarrow
&& (rr
.auth
|| (rr
.qtype
.getCode() == QType::NS
&& (!zs
.optOutFlag
|| zs
.secured
.count(ordername
))))) {
571 di
.backend
->feedRecord(rr
, ordername
, true);
573 di
.backend
->feedRecord(rr
, DNSName());
576 if (rr
.auth
|| rr
.qtype
.getCode() == QType::NS
) {
577 ordername
=rr
.qname
.makeRelative(domain
);
578 di
.backend
->feedRecord(rr
, ordername
);
580 di
.backend
->feedRecord(rr
, DNSName());
583 di
.backend
->feedRecord(rr
, DNSName());
586 // Insert empty non-terminals
587 if(doent
&& !nonterm
.empty()) {
589 di
.backend
->feedEnts3(zs
.domain_id
, domain
, nonterm
, zs
.ns3pr
, zs
.isNarrow
);
591 di
.backend
->feedEnts(zs
.domain_id
, nonterm
);
594 di
.backend
->commitTransaction();
596 di
.backend
->setFresh(zs
.domain_id
);
597 purgeAuthCaches(domain
.toString()+"$");
599 g_log
<<Logger::Error
<<"AXFR done for '"<<domain
<<"', zone committed with serial number "<<zs
.soa_serial
<<endl
;
601 // Send slave re-notifications
604 if(B
.getDomainMetadata(domain
, "SLAVE-RENOTIFY", meta
) && !meta
.empty()) {
605 doNotify
=(meta
.front() == "1");
607 doNotify
=(::arg().mustDo("slave-renotify"));
610 notifyDomain(domain
, &B
);
614 catch(DBException
&re
) {
615 g_log
<<Logger::Error
<<"Unable to feed record during incoming AXFR of '" << domain
<<"': "<<re
.reason
<<endl
;
616 if(di
.backend
&& transaction
) {
617 g_log
<<Logger::Error
<<"Aborting possible open transaction for domain '"<<domain
<<"' AXFR"<<endl
;
618 di
.backend
->abortTransaction();
621 catch(const MOADNSException
&mde
) {
622 g_log
<<Logger::Error
<<"Unable to parse record during incoming AXFR of '"<<domain
<<"' (MOADNSException): "<<mde
.what()<<endl
;
623 if(di
.backend
&& transaction
) {
624 g_log
<<Logger::Error
<<"Aborting possible open transaction for domain '"<<domain
<<"' AXFR"<<endl
;
625 di
.backend
->abortTransaction();
628 catch(std::exception
&re
) {
629 g_log
<<Logger::Error
<<"Unable to parse record during incoming AXFR of '"<<domain
<<"' (std::exception): "<<re
.what()<<endl
;
630 if(di
.backend
&& transaction
) {
631 g_log
<<Logger::Error
<<"Aborting possible open transaction for domain '"<<domain
<<"' AXFR"<<endl
;
632 di
.backend
->abortTransaction();
635 catch(ResolverException
&re
) {
638 // The AXFR probably failed due to a problem on the master server. If SOA-checks against this master
639 // still succeed, we would constantly try to AXFR the zone. To avoid this, we add the zone to the list of
640 // failed slave-checks. This will suspend slave-checks (and subsequent AXFR) for this zone for some time.
641 uint64_t newCount
= 1;
642 time_t now
= time(0);
643 const auto failedEntry
= d_failedSlaveRefresh
.find(domain
);
644 if (failedEntry
!= d_failedSlaveRefresh
.end())
645 newCount
= d_failedSlaveRefresh
[domain
].first
+ 1;
646 time_t nextCheck
= now
+ std::min(newCount
* d_tickinterval
, (uint64_t)::arg().asNum("soa-retry-default"));
647 d_failedSlaveRefresh
[domain
] = {newCount
, nextCheck
};
648 g_log
<<Logger::Error
<<"Unable to AXFR zone '"<<domain
<<"' from remote '"<<remote
<<"' (resolver): "<<re
.reason
<<" (This was the "<<(newCount
== 1 ? "first" : std::to_string(newCount
) + "th")<<" time. Excluding zone from slave-checks until "<<nextCheck
<<")"<<endl
;
650 if(di
.backend
&& transaction
) {
651 g_log
<<Logger::Error
<<"Aborting possible open transaction for domain '"<<domain
<<"' AXFR"<<endl
;
652 di
.backend
->abortTransaction();
655 catch(PDNSException
&ae
) {
656 g_log
<<Logger::Error
<<"Unable to AXFR zone '"<<domain
<<"' from remote '"<<remote
<<"' (PDNSException): "<<ae
.reason
<<endl
;
657 if(di
.backend
&& transaction
) {
658 g_log
<<Logger::Error
<<"Aborting possible open transaction for domain '"<<domain
<<"' AXFR"<<endl
;
659 di
.backend
->abortTransaction();
664 struct DomainNotificationInfo
668 ComboAddress localaddr
;
669 DNSName tsigkeyname
, tsigalgname
;
675 struct SlaveSenderReceiver
677 typedef std::tuple
<DNSName
, ComboAddress
, uint16_t> Identifier
;
680 uint32_t theirSerial
;
681 uint32_t theirInception
;
682 uint32_t theirExpire
;
685 map
<uint32_t, Answer
> d_freshness
;
687 SlaveSenderReceiver()
691 void deliverTimeout(const Identifier
& i
)
695 Identifier
send(DomainNotificationInfo
& dni
)
697 random_shuffle(dni
.di
.masters
.begin(), dni
.di
.masters
.end());
699 return std::make_tuple(dni
.di
.zone
,
700 *dni
.di
.masters
.begin(),
701 d_resolver
.sendResolve(*dni
.di
.masters
.begin(),
706 dni
.dnssecOk
, dni
.tsigkeyname
, dni
.tsigalgname
, dni
.tsigsecret
)
709 catch(PDNSException
& e
) {
710 throw runtime_error("While attempting to query freshness of '"+dni
.di
.zone
.toLogString()+"': "+e
.reason
);
714 bool receive(Identifier
& id
, Answer
& a
)
716 if(d_resolver
.tryGetSOASerial(&(std::get
<0>(id
)), &(std::get
<1>(id
)), &a
.theirSerial
, &a
.theirInception
, &a
.theirExpire
, &(std::get
<2>(id
)))) {
722 void deliverAnswer(DomainNotificationInfo
& dni
, const Answer
& a
, unsigned int usec
)
724 d_freshness
[dni
.di
.id
]=a
;
730 void CommunicatorClass::addSlaveCheckRequest(const DomainInfo
& di
, const ComboAddress
& remote
)
733 DomainInfo ours
= di
;
736 // When adding a check, if the remote addr from which notification was
737 // received is a master, clear all other masters so we can be sure the
738 // query goes to that one.
739 for (const auto& master
: di
.masters
) {
740 if (ComboAddress::addressOnlyEqual()(remote
, master
)) {
741 ours
.masters
.clear();
742 ours
.masters
.push_back(master
);
747 d_tocheck
.insert(ours
);
748 d_any_sem
.post(); // kick the loop!
751 void CommunicatorClass::addTrySuperMasterRequest(const DNSPacket
& p
)
755 if(d_potentialsupermasters
.insert(ours
).second
)
756 d_any_sem
.post(); // kick the loop!
759 void CommunicatorClass::slaveRefresh(PacketHandler
*P
)
761 // not unless we are slave
762 if (!::arg().mustDo("slave")) return;
764 UeberBackend
*B
=P
->getBackend();
765 vector
<DomainInfo
> rdomains
;
766 vector
<DomainNotificationInfo
> sdomains
;
767 set
<DNSPacket
, cmp
> trysuperdomains
;
770 set
<DomainInfo
> requeue
;
771 for(const auto& di
: d_tocheck
) {
772 if(d_inprogress
.count(di
.zone
)) {
773 g_log
<<Logger::Debug
<<"Got NOTIFY for "<<di
.zone
<<" while AXFR in progress, requeueing SOA check"<<endl
;
777 // We received a NOTIFY for a zone. This means at least one of the zone's master server is working.
778 // Therefore we delete the zone from the list of failed slave-checks to allow immediate checking.
779 const auto wasFailedDomain
= d_failedSlaveRefresh
.find(di
.zone
);
780 if (wasFailedDomain
!= d_failedSlaveRefresh
.end()) {
781 g_log
<<Logger::Debug
<<"Got NOTIFY for "<<di
.zone
<<", removing zone from list of failed slave-checks and going to check SOA serial"<<endl
;
782 d_failedSlaveRefresh
.erase(di
.zone
);
784 g_log
<<Logger::Debug
<<"Got NOTIFY for "<<di
.zone
<<", going to check SOA serial"<<endl
;
786 rdomains
.push_back(di
);
789 d_tocheck
.swap(requeue
);
791 trysuperdomains
= d_potentialsupermasters
;
792 d_potentialsupermasters
.clear();
795 for(const DNSPacket
& dp
: trysuperdomains
) {
796 // get the TSIG key name
797 TSIGRecordContent trc
;
799 dp
.getTSIGDetails(&trc
, &tsigkeyname
);
800 P
->trySuperMasterSynchronous(dp
, tsigkeyname
); // FIXME could use some error loging
802 if(rdomains
.empty()) { // if we have priority domains, check them first
803 B
->getUnfreshSlaveInfos(&rdomains
);
805 DNSSECKeeper
dk(B
); // NOW HEAR THIS! This DK uses our B backend, so no interleaved access!
808 domains_by_name_t
& nameindex
=boost::multi_index::get
<IDTag
>(d_suckdomains
);
809 time_t now
= time(0);
811 for(DomainInfo
& di
: rdomains
) {
812 const auto failed
= d_failedSlaveRefresh
.find(di
.zone
);
813 if (failed
!= d_failedSlaveRefresh
.end() && now
< failed
->second
.second
) {
814 // If the domain has failed before and the time before the next check has not expired, skip this domain
815 g_log
<<Logger::Debug
<<"Zone '"<<di
.zone
<<"' is on the list of failed SOA checks. Skipping SOA checks until "<< failed
->second
.second
<<endl
;
818 std::vector
<std::string
> localaddr
;
821 if(di
.masters
.empty()) // slave domains w/o masters are ignored
823 // remove unfresh domains already queued for AXFR, no sense polling them again
824 sr
.master
=*di
.masters
.begin();
825 if(nameindex
.count(sr
)) { // this does NOT however protect us against AXFRs already in progress!
828 if(d_inprogress
.count(sr
.domain
)) // this does
831 DomainNotificationInfo dni
;
833 dni
.dnssecOk
= dk
.doesDNSSEC();
835 if(dk
.getTSIGForAccess(di
.zone
, sr
.master
, &dni
.tsigkeyname
)) {
837 if(!B
->getTSIGKey(dni
.tsigkeyname
, &dni
.tsigalgname
, &secret64
)) {
838 g_log
<<Logger::Error
<<"TSIG key '"<<dni
.tsigkeyname
<<"' for domain '"<<di
.zone
<<"' not found, can not AXFR."<<endl
;
841 if (B64Decode(secret64
, dni
.tsigsecret
) == -1) {
842 g_log
<<Logger::Error
<<"Unable to Base-64 decode TSIG key '"<<dni
.tsigkeyname
<<"' for domain '"<<di
.zone
<<"', can not AXFR."<<endl
;
848 // check for AXFR-SOURCE
849 if(B
->getDomainMetadata(di
.zone
, "AXFR-SOURCE", localaddr
) && !localaddr
.empty()) {
851 dni
.localaddr
= ComboAddress(localaddr
[0]);
852 g_log
<<Logger::Info
<<"Freshness check source (AXFR-SOURCE) for domain '"<<di
.zone
<<"' set to "<<localaddr
[0]<<endl
;
854 catch(std::exception
& e
) {
855 g_log
<<Logger::Error
<<"Failed to load freshness check source '"<<localaddr
[0]<<"' for '"<<di
.zone
<<"': "<<e
.what()<<endl
;
859 dni
.localaddr
.sin4
.sin_family
= 0;
862 sdomains
.push_back(dni
);
867 if(d_slaveschanged
) {
869 g_log
<<Logger::Warning
<<"No new unfresh slave domains, "<<d_suckdomains
.size()<<" queued for AXFR already, "<<d_inprogress
.size()<<" in progress"<<endl
;
871 d_slaveschanged
= !rdomains
.empty();
876 g_log
<<Logger::Warning
<<sdomains
.size()<<" slave domain"<<(sdomains
.size()>1 ? "s" : "")<<" need"<<
877 (sdomains
.size()>1 ? "" : "s")<<
878 " checking, "<<d_suckdomains
.size()<<" queued for AXFR"<<endl
;
881 SlaveSenderReceiver ssr
;
883 Inflighter
<vector
<DomainNotificationInfo
>, SlaveSenderReceiver
> ifl(sdomains
, ssr
);
885 ifl
.d_maxInFlight
= 200;
892 catch(std::exception
& e
) {
893 g_log
<<Logger::Error
<<"While checking domain freshness: " << e
.what()<<endl
;
895 catch(PDNSException
&re
) {
896 g_log
<<Logger::Error
<<"While checking domain freshness: " << re
.reason
<<endl
;
899 g_log
<<Logger::Warning
<<"Received serial number updates for "<<ssr
.d_freshness
.size()<<" zone"<<addS(ssr
.d_freshness
.size())<<", had "<<ifl
.getTimeouts()<<" timeout"<<addS(ifl
.getTimeouts())<<endl
;
901 typedef DomainNotificationInfo val_t
;
902 time_t now
= time(0);
903 for(val_t
& val
: sdomains
) {
904 DomainInfo
& di(val
.di
);
906 // might've come from the packethandler
907 // Please do not overwrite received DI just to make sure it exists in backend.
909 if (!B
->getDomainInfo(di
.zone
, tempdi
)) {
910 g_log
<<Logger::Warning
<<"Ignore domain "<< di
.zone
<<" since it has been removed from our backend"<<endl
;
913 // Backend for di still doesn't exist and this might cause us to
914 // SEGFAULT on the setFresh command later on
915 di
.backend
= tempdi
.backend
;
918 if(!ssr
.d_freshness
.count(di
.id
)) { // If we don't have an answer for the domain
919 uint64_t newCount
= 1;
921 const auto failedEntry
= d_failedSlaveRefresh
.find(di
.zone
);
922 if (failedEntry
!= d_failedSlaveRefresh
.end())
923 newCount
= d_failedSlaveRefresh
[di
.zone
].first
+ 1;
924 time_t nextCheck
= now
+ std::min(newCount
* d_tickinterval
, (uint64_t)::arg().asNum("soa-retry-default"));
925 d_failedSlaveRefresh
[di
.zone
] = {newCount
, nextCheck
};
927 g_log
<<Logger::Warning
<<"Unable to retrieve SOA for "<<di
.zone
<<
928 ", this was the first time. NOTE: For every subsequent failed SOA check the domain will be suspended from freshness checks for 'num-errors x "<<
929 d_tickinterval
<<" seconds', with a maximum of "<<(uint64_t)::arg().asNum("soa-retry-default")<<" seconds. Skipping SOA checks until "<<nextCheck
<<endl
;
930 } else if (newCount
% 10 == 0) {
931 g_log
<<Logger::Warning
<<"Unable to retrieve SOA for "<<di
.zone
<<", this was the "<<std::to_string(newCount
)<<"th time. Skipping SOA checks until "<<nextCheck
<<endl
;
938 const auto wasFailedDomain
= d_failedSlaveRefresh
.find(di
.zone
);
939 if (wasFailedDomain
!= d_failedSlaveRefresh
.end())
940 d_failedSlaveRefresh
.erase(di
.zone
);
946 hasSOA
= B
->getSOA(di
.zone
, sd
);
950 uint32_t theirserial
= ssr
.d_freshness
[di
.id
].theirSerial
, ourserial
= sd
.serial
;
952 if(rfc1982LessThan(theirserial
, ourserial
) && ourserial
!= 0 && !::arg().mustDo("axfr-lower-serial")) {
953 g_log
<<Logger::Error
<<"Domain '"<<di
.zone
<<"' more recent than master, our serial " << ourserial
<< " > their serial "<< theirserial
<< endl
;
954 di
.backend
->setFresh(di
.id
);
956 else if(hasSOA
&& theirserial
== ourserial
) {
957 uint32_t maxExpire
=0, maxInception
=0;
958 if(dk
.isPresigned(di
.zone
)) {
959 B
->lookup(QType(QType::RRSIG
), di
.zone
, di
.id
); // can't use DK before we are done with this lookup!
962 auto rrsig
= getRR
<RRSIGRecordContent
>(zr
.dr
);
963 if(rrsig
->d_type
== QType::SOA
) {
964 maxInception
= std::max(maxInception
, rrsig
->d_siginception
);
965 maxExpire
= std::max(maxExpire
, rrsig
->d_sigexpire
);
969 if(! maxInception
&& ! ssr
.d_freshness
[di
.id
].theirInception
) {
970 g_log
<<Logger::Info
<<"Domain '"<< di
.zone
<<"' is fresh (no DNSSEC), serial is "<<ourserial
<<endl
;
971 di
.backend
->setFresh(di
.id
);
973 else if(maxInception
== ssr
.d_freshness
[di
.id
].theirInception
&& maxExpire
== ssr
.d_freshness
[di
.id
].theirExpire
) {
974 g_log
<<Logger::Info
<<"Domain '"<< di
.zone
<<"' is fresh and SOA RRSIGs match, serial is "<<ourserial
<<endl
;
975 di
.backend
->setFresh(di
.id
);
977 else if(maxExpire
>= now
&& ! ssr
.d_freshness
[di
.id
].theirInception
) {
978 g_log
<<Logger::Info
<<"Domain '"<< di
.zone
<<"' is fresh, master is no longer signed but (some) signatures are still vallid, serial is "<<ourserial
<<endl
;
979 di
.backend
->setFresh(di
.id
);
981 else if(maxInception
&& ! ssr
.d_freshness
[di
.id
].theirInception
) {
982 g_log
<<Logger::Warning
<<"Domain '"<< di
.zone
<<"' is stale, master is no longer signed and all signatures have expired, serial is "<<ourserial
<<endl
;
983 addSuckRequest(di
.zone
, *di
.masters
.begin());
985 else if(dk
.doesDNSSEC() && ! maxInception
&& ssr
.d_freshness
[di
.id
].theirInception
) {
986 g_log
<<Logger::Warning
<<"Domain '"<< di
.zone
<<"' is stale, master has signed, serial is "<<ourserial
<<endl
;
987 addSuckRequest(di
.zone
, *di
.masters
.begin());
990 g_log
<<Logger::Warning
<<"Domain '"<< di
.zone
<<"' is fresh, but RRSIGs differ, so DNSSEC is stale, serial is "<<ourserial
<<endl
;
991 addSuckRequest(di
.zone
, *di
.masters
.begin());
996 g_log
<<Logger::Warning
<<"Domain '"<< di
.zone
<<"' is stale, master serial "<<theirserial
<<", our serial "<< ourserial
<<endl
;
999 g_log
<<Logger::Warning
<<"Domain '"<< di
.zone
<<"' is empty, master serial "<<theirserial
<<endl
;
1001 addSuckRequest(di
.zone
, *di
.masters
.begin());