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 "axfr-retriever.hh"
40 #include "arguments.hh"
41 #include "auth-caches.hh"
44 #include "inflighter.cc"
45 #include "namespaces.hh"
46 #include "auth-main.hh"
47 #include "query-local-address.hh"
51 void CommunicatorClass::addSuckRequest(const DNSName
& domain
, const ComboAddress
& primary
, SuckRequest::RequestPriority priority
, bool force
)
53 auto data
= d_data
.lock();
58 sr
.priorityAndOrder
.first
= priority
;
59 sr
.priorityAndOrder
.second
= data
->d_sorthelper
++;
60 pair
<UniQueue::iterator
, bool> res
;
62 res
= data
->d_suckdomains
.insert(sr
);
67 data
->d_suckdomains
.modify(res
.first
, [priorityAndOrder
= sr
.priorityAndOrder
](SuckRequest
& so
) {
68 if (priorityAndOrder
.first
< so
.priorityAndOrder
.first
) {
69 so
.priorityAndOrder
= priorityAndOrder
;
77 bool isDnssecZone
{false};
78 bool isPresigned
{false};
80 bool optOutFlag
{false};
81 NSEC3PARAMRecordContent ns3pr
;
84 unsigned int soa_serial
{0};
85 set
<DNSName
> nsset
, qnames
, secured
;
90 static bool catalogDiff(const DomainInfo
& di
, vector
<CatalogInfo
>& fromXFR
, vector
<CatalogInfo
>& fromDB
, const string
& logPrefix
)
92 extern CommunicatorClass Communicator
;
94 bool doTransaction
{true};
95 bool inTransaction
{false};
96 CatalogInfo ciCreate
, ciRemove
;
97 std::unordered_map
<DNSName
, bool> clearCache
;
98 vector
<CatalogInfo
> retrieve
;
101 sort(fromXFR
.begin(), fromXFR
.end());
102 sort(fromDB
.begin(), fromDB
.end());
104 auto xfr
= fromXFR
.cbegin();
105 auto db
= fromDB
.cbegin();
107 while (xfr
!= fromXFR
.end() || db
!= fromDB
.end()) {
111 if (xfr
!= fromXFR
.end() && (db
== fromDB
.end() || *xfr
< *db
)) { // create
116 else if (db
!= fromDB
.end() && (xfr
== fromXFR
.end() || *db
< *xfr
)) { // remove
122 CatalogInfo ciXFR
= *xfr
;
123 CatalogInfo ciDB
= *db
;
124 if (ciDB
.d_unique
.empty() || ciXFR
.d_unique
== ciDB
.d_unique
) { // update
125 bool doOptions
{false};
127 if (ciDB
.d_unique
.empty()) { // set unique
128 g_log
<< Logger::Warning
<< logPrefix
<< "set unique, zone '" << ciXFR
.d_zone
<< "' is now a member" << endl
;
129 ciDB
.d_unique
= ciXFR
.d_unique
;
133 if (ciXFR
.d_coo
!= ciDB
.d_coo
) { // update coo
134 g_log
<< Logger::Warning
<< logPrefix
<< "update coo for zone '" << ciXFR
.d_zone
<< "' to '" << ciXFR
.d_coo
<< "'" << endl
;
135 ciDB
.d_coo
= ciXFR
.d_coo
;
139 if (ciXFR
.d_group
!= ciDB
.d_group
) { // update group
140 g_log
<< Logger::Warning
<< logPrefix
<< "update group for zone '" << ciXFR
.d_zone
<< "' to '" << boost::join(ciXFR
.d_group
, ", ") << "'" << endl
;
141 ciDB
.d_group
= ciXFR
.d_group
;
145 if (doOptions
) { // update zone options
146 if (doTransaction
&& (inTransaction
= di
.backend
->startTransaction(di
.zone
))) {
147 g_log
<< Logger::Warning
<< logPrefix
<< "backend transaction started" << endl
;
148 doTransaction
= false;
151 g_log
<< Logger::Warning
<< logPrefix
<< "update options for zone '" << ciXFR
.d_zone
<< "'" << endl
;
152 di
.backend
->setOptions(ciXFR
.d_zone
, ciDB
.toJson());
155 if (di
.primaries
!= ciDB
.d_primaries
) { // update primaries
156 if (doTransaction
&& (inTransaction
= di
.backend
->startTransaction(di
.zone
))) {
157 g_log
<< Logger::Warning
<< logPrefix
<< "backend transaction started" << endl
;
158 doTransaction
= false;
161 vector
<string
> primaries
;
162 for (const auto& primary
: di
.primaries
) {
163 primaries
.push_back(primary
.toStringWithPortExcept(53));
165 g_log
<< Logger::Warning
<< logPrefix
<< "update primaries for zone '" << ciXFR
.d_zone
<< "' to '" << boost::join(primaries
, ", ") << "'" << endl
;
166 di
.backend
->setPrimaries(ciXFR
.d_zone
, di
.primaries
);
168 retrieve
.emplace_back(ciXFR
);
182 if (create
&& remove
) {
183 g_log
<< Logger::Warning
<< logPrefix
<< "zone '" << ciCreate
.d_zone
<< "' state reset" << endl
;
185 else if (create
&& di
.backend
->getDomainInfo(ciCreate
.d_zone
, d
)) { // detect clash
187 ci
.fromJson(d
.options
, CatalogInfo::CatalogType::Consumer
);
189 if (di
.zone
!= d
.catalog
&& di
.zone
== ci
.d_coo
) {
190 if (ciCreate
.d_unique
== ci
.d_unique
) {
191 g_log
<< Logger::Warning
<< logPrefix
<< "zone '" << d
.zone
<< "' owner change without state reset, old catalog '" << d
.catalog
<< "', new catalog '" << di
.zone
<< "'" << endl
;
193 if (doTransaction
&& (inTransaction
= di
.backend
->startTransaction(di
.zone
))) {
194 g_log
<< Logger::Warning
<< logPrefix
<< "backend transaction started" << endl
;
195 doTransaction
= false;
198 di
.backend
->setPrimaries(ciCreate
.d_zone
, di
.primaries
);
199 di
.backend
->setOptions(ciCreate
.d_zone
, ciCreate
.toJson());
200 di
.backend
->setCatalog(ciCreate
.d_zone
, di
.zone
);
202 retrieve
.emplace_back(ciCreate
);
205 g_log
<< Logger::Warning
<< logPrefix
<< "zone '" << d
.zone
<< "' owner change with state reset, old catalog '" << d
.catalog
<< "', new catalog '" << di
.zone
<< "'" << endl
;
207 ciRemove
.d_zone
= d
.zone
;
211 g_log
<< Logger::Warning
<< logPrefix
<< "zone '" << d
.zone
<< "' already exists";
212 if (!d
.catalog
.empty()) {
213 g_log
<< " in catalog '" << d
.catalog
;
215 g_log
<< "', create skipped" << endl
;
220 if (remove
) { // delete zone
221 if (doTransaction
&& (inTransaction
= di
.backend
->startTransaction(di
.zone
))) {
222 g_log
<< Logger::Warning
<< logPrefix
<< "backend transaction started" << endl
;
223 doTransaction
= false;
226 g_log
<< Logger::Warning
<< logPrefix
<< "delete zone '" << ciRemove
.d_zone
<< "'" << endl
;
227 di
.backend
->deleteDomain(ciRemove
.d_zone
);
230 clearCache
[ciRemove
.d_zone
] = false;
234 if (create
) { // create zone
235 if (doTransaction
&& (inTransaction
= di
.backend
->startTransaction(di
.zone
))) {
236 g_log
<< Logger::Warning
<< logPrefix
<< "backend transaction started" << endl
;
237 doTransaction
= false;
240 g_log
<< Logger::Warning
<< logPrefix
<< "create zone '" << ciCreate
.d_zone
<< "'" << endl
;
241 di
.backend
->createDomain(ciCreate
.d_zone
, DomainInfo::Secondary
, ciCreate
.d_primaries
, "");
243 di
.backend
->setPrimaries(ciCreate
.d_zone
, di
.primaries
);
244 di
.backend
->setOptions(ciCreate
.d_zone
, ciCreate
.toJson());
245 di
.backend
->setCatalog(ciCreate
.d_zone
, di
.zone
);
247 clearCache
[ciCreate
.d_zone
] = true;
248 retrieve
.emplace_back(ciCreate
);
252 if (inTransaction
&& di
.backend
->commitTransaction()) {
253 g_log
<< Logger::Warning
<< logPrefix
<< "backend transaction committed" << endl
;
256 // Update zonecache and clear all caches
258 for (const auto& zone
: clearCache
) {
259 if (g_zoneCache
.isEnabled()) {
261 if (di
.backend
->getDomainInfo(zone
.first
, d
)) {
262 g_zoneCache
.add(zone
.first
, d
.id
);
265 g_log
<< Logger::Error
<< logPrefix
<< "new zone '" << zone
.first
<< "' does not exists and was not inserted in the zone-cache" << endl
;
269 g_zoneCache
.remove(zone
.first
);
273 DNSSECKeeper::clearCaches(zone
.first
);
274 purgeAuthCaches(zone
.first
.toString() + "$");
277 // retrieve new and updated zones with new primaries
278 auto primaries
= di
.primaries
;
279 if (!primaries
.empty()) {
280 for (auto& ret
: retrieve
) {
281 shuffle(primaries
.begin(), primaries
.end(), pdns::dns_random_engine());
282 const auto& primary
= primaries
.front();
283 Communicator
.addSuckRequest(ret
.d_zone
, primary
, SuckRequest::Notify
);
289 catch (DBException
& re
) {
290 g_log
<< Logger::Error
<< logPrefix
<< "DBException " << re
.reason
<< endl
;
292 catch (PDNSException
& pe
) {
293 g_log
<< Logger::Error
<< logPrefix
<< "PDNSException " << pe
.reason
<< endl
;
295 catch (std::exception
& re
) {
296 g_log
<< Logger::Error
<< logPrefix
<< "std::exception " << re
.what() << endl
;
299 if (di
.backend
&& inTransaction
) {
300 g_log
<< Logger::Info
<< logPrefix
<< "aborting possible open transaction" << endl
;
301 di
.backend
->abortTransaction();
307 static bool catalogProcess(const DomainInfo
& di
, vector
<DNSResourceRecord
>& rrs
, string logPrefix
)
309 logPrefix
+= "Catalog-Zone ";
311 vector
<CatalogInfo
> fromXFR
, fromDB
;
312 std::unordered_set
<DNSName
> dupcheck
;
316 bool zoneInvalid
{false};
321 vector
<DNSResourceRecord
> ret
;
323 const auto compare
= [](const DNSResourceRecord
& a
, const DNSResourceRecord
& b
) { return a
.qname
== b
.qname
? a
.qtype
< b
.qtype
: a
.qname
.canonCompare(b
.qname
); };
324 sort(rrs
.begin(), rrs
.end(), compare
);
328 for (auto& rr
: rrs
) {
329 if (di
.zone
== rr
.qname
) {
330 if (rr
.qtype
== QType::SOA
) {
334 if (rr
.qtype
== QType::NS
) {
339 else if (rr
.qname
== DNSName("version") + di
.zone
&& rr
.qtype
== QType::TXT
) {
341 g_log
<< Logger::Warning
<< logPrefix
<< "zone '" << di
.zone
<< "', multiple version records found, aborting" << endl
;
345 if (rr
.content
== "\"1\"") {
348 else if (rr
.content
== "\"2\"") {
352 g_log
<< Logger::Warning
<< logPrefix
<< "zone '" << di
.zone
<< "', unsupported catalog zone schema version " << rr
.content
<< ", aborting" << endl
;
357 else if (rr
.qname
.isPartOf(DNSName("zones") + di
.zone
)) {
358 if (rel
.empty() && !hasVersion
) {
359 g_log
<< Logger::Warning
<< logPrefix
<< "zone '" << di
.zone
<< "', catalog zone schema version missing, aborting" << endl
;
363 rel
= rr
.qname
.makeRelative(DNSName("zones") + di
.zone
);
365 if (rel
.countLabels() == 1 && rr
.qtype
== QType::PTR
) {
366 if (!unique
.empty()) {
368 fromXFR
.emplace_back(ci
);
371 g_log
<< Logger::Warning
<< logPrefix
<< "zone '" << di
.zone
<< "', duplicate unique '" << unique
<< "'" << endl
;
379 ci
.setType(CatalogInfo::CatalogType::Consumer
);
380 ci
.d_zone
= DNSName(rr
.content
);
381 ci
.d_unique
= unique
;
383 if (!dupcheck
.insert(ci
.d_zone
).second
) {
384 g_log
<< Logger::Warning
<< logPrefix
<< "zone '" << di
.zone
<< "', duplicate member zone'" << ci
.d_zone
<< "'" << endl
;
389 else if (hasVersion
== 2) {
390 if (rel
== (DNSName("coo") + unique
) && rr
.qtype
== QType::PTR
) {
391 if (!ci
.d_coo
.empty()) {
392 g_log
<< Logger::Warning
<< logPrefix
<< "zone '" << di
.zone
<< "', duplicate COO for unique '" << unique
<< "'" << endl
;
396 ci
.d_coo
= DNSName(rr
.content
);
399 else if (rel
== (DNSName("group") + unique
) && rr
.qtype
== QType::TXT
) {
400 std::string content
= rr
.content
;
401 if (content
.length() >= 2 && content
.at(0) == '\"' && content
.at(content
.length() - 1) == '\"') { // TXT pain
402 content
= content
.substr(1, content
.length() - 2);
404 ci
.d_group
.insert(content
);
410 if (!ci
.d_zone
.empty()) {
411 fromXFR
.emplace_back(ci
);
414 if (!hasSOA
|| !hasVersion
|| zoneInvalid
) {
415 g_log
<< Logger::Warning
<< logPrefix
<< "zone '" << di
.zone
<< "' is invalid, skip updates" << endl
;
419 // Get catalog ifo from db
420 if (!di
.backend
->getCatalogMembers(di
.zone
, fromDB
, CatalogInfo::CatalogType::Consumer
)) {
425 return catalogDiff(di
, fromXFR
, fromDB
, logPrefix
);
428 void CommunicatorClass::ixfrSuck(const DNSName
& domain
, const TSIGTriplet
& tt
, const ComboAddress
& laddr
, const ComboAddress
& remote
, ZoneStatus
& zs
, vector
<DNSRecord
>* axfr
)
430 string logPrefix
= "IXFR-in zone '" + domain
.toLogString() + "', primary '" + remote
.toString() + "', ";
432 UeberBackend B
; // fresh UeberBackend
435 di
.backend
= nullptr;
436 // bool transaction=false;
438 DNSSECKeeper
dk(&B
); // reuse our UeberBackend copy for DNSSECKeeper
440 bool wrongDomainKind
= false;
441 // this checks three error conditions, and sets wrongDomainKind if we hit the third & had an error
442 if (!B
.getDomainInfo(domain
, di
) || !di
.backend
|| (wrongDomainKind
= true, di
.kind
!= DomainInfo::Secondary
)) { // di.backend and B are mostly identical
444 g_log
<< Logger::Warning
<< logPrefix
<< "can't determine backend, not configured as secondary" << endl
;
446 g_log
<< Logger::Warning
<< logPrefix
<< "can't determine backend" << endl
;
450 uint16_t xfrTimeout
= ::arg().asNum("axfr-fetch-timeout");
452 memset(&st
, 0, sizeof(st
));
453 st
.serial
= di
.serial
;
456 drsoa
.setContent(std::make_shared
<SOARecordContent
>(g_rootdnsname
, g_rootdnsname
, st
));
457 auto deltas
= getIXFRDeltas(remote
, domain
, drsoa
, xfrTimeout
, false, tt
, laddr
.sin4
.sin_family
? &laddr
: nullptr, ((size_t)::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024);
458 zs
.numDeltas
= deltas
.size();
459 // cout<<"Got "<<deltas.size()<<" deltas from serial "<<di.serial<<", applying.."<<endl;
461 for (const auto& d
: deltas
) {
462 const auto& remove
= d
.first
;
463 const auto& add
= d
.second
;
464 // cout<<"Delta sizes: "<<remove.size()<<", "<<add.size()<<endl;
466 if (remove
.empty()) { // we got passed an AXFR!
471 // our hammer is 'replaceRRSet(domain_id, qname, qt, vector<DNSResourceRecord>& rrset)
472 // which thinks in terms of RRSETs
473 // however, IXFR does not, and removes and adds *records* (bummer)
474 // this means that we must group updates by {qname,qtype}, retrieve the RRSET, apply
475 // the add/remove updates, and replaceRRSet the whole thing.
477 map
<pair
<DNSName
, uint16_t>, pair
<vector
<DNSRecord
>, vector
<DNSRecord
>>> grouped
;
479 for (const auto& x
: remove
)
480 grouped
[{x
.d_name
, x
.d_type
}].first
.push_back(x
);
481 for (const auto& x
: add
)
482 grouped
[{x
.d_name
, x
.d_type
}].second
.push_back(x
);
484 di
.backend
->startTransaction(domain
, -1);
485 for (const auto& g
: grouped
) {
486 vector
<DNSRecord
> rrset
;
489 di
.backend
->lookup(QType(g
.first
.second
), g
.first
.first
+ domain
, di
.id
);
490 while (di
.backend
->get(zrr
)) {
491 zrr
.dr
.d_name
.makeUsRelative(domain
);
492 rrset
.push_back(zrr
.dr
);
496 rrset
.erase(remove_if(rrset
.begin(), rrset
.end(),
497 [&g
](const DNSRecord
& dr
) {
498 return count(g
.second
.first
.cbegin(),
499 g
.second
.first
.cend(), dr
);
502 // the DNSRecord== operator compares on name, type, class and lowercase content representation
504 for (const auto& x
: g
.second
.second
) {
508 vector
<DNSResourceRecord
> replacement
;
509 for (const auto& dr
: rrset
) {
510 auto rr
= DNSResourceRecord::fromWire(dr
);
512 rr
.domain_id
= di
.id
;
513 if (dr
.d_type
== QType::SOA
) {
514 // cout<<"New SOA: "<<x.d_content->getZoneRepresentation()<<endl;
515 auto sr
= getRR
<SOARecordContent
>(dr
);
516 zs
.soa_serial
= sr
->d_st
.serial
;
519 replacement
.push_back(rr
);
522 di
.backend
->replaceRRSet(di
.id
, g
.first
.first
+ domain
, QType(g
.first
.second
), replacement
);
524 di
.backend
->commitTransaction();
527 catch (std::exception
& p
) {
528 g_log
<< Logger::Error
<< logPrefix
<< "got exception (std::exception): " << p
.what() << endl
;
531 catch (PDNSException
& p
) {
532 g_log
<< Logger::Error
<< logPrefix
<< "got exception (PDNSException): " << p
.reason
<< endl
;
537 static bool processRecordForZS(const DNSName
& domain
, bool& firstNSEC3
, DNSResourceRecord
& rr
, ZoneStatus
& zs
)
539 switch (rr
.qtype
.getCode()) {
540 case QType::NSEC3PARAM
:
541 zs
.ns3pr
= NSEC3PARAMRecordContent(rr
.content
);
542 zs
.isDnssecZone
= zs
.isNSEC3
= true;
546 NSEC3RecordContent
ns3rc(rr
.content
);
548 zs
.isDnssecZone
= zs
.isPresigned
= true;
551 else if (zs
.optOutFlag
!= (ns3rc
.d_flags
& 1))
552 throw PDNSException("Zones with a mixture of Opt-Out NSEC3 RRs and non-Opt-Out NSEC3 RRs are not supported.");
553 zs
.optOutFlag
= ns3rc
.d_flags
& 1;
554 if (ns3rc
.isSet(QType::NS
) && !(rr
.qname
== domain
)) {
555 DNSName hashPart
= rr
.qname
.makeRelative(domain
);
556 zs
.secured
.insert(hashPart
);
562 zs
.isDnssecZone
= zs
.isPresigned
= true;
566 if (rr
.qname
!= domain
)
567 zs
.nsset
.insert(rr
.qname
);
571 zs
.qnames
.insert(rr
.qname
);
573 rr
.domain_id
= zs
.domain_id
;
577 /* So this code does a number of things.
578 1) It will AXFR a domain from a primary
579 The code can retrieve the current serial number in the database itself.
580 It may attempt an IXFR
581 2) It will filter the zone through a lua *filter* script
582 3) The code walks through the zone records do determine DNSSEC status (secured, nsec/nsec3, optout)
583 4) It inserts the zone into the database
584 With the right 'ordername' fields
585 5) It updates the Empty Non Terminals
588 static vector
<DNSResourceRecord
> doAxfr(const ComboAddress
& raddr
, const DNSName
& domain
, const TSIGTriplet
& tt
, const ComboAddress
& laddr
, unique_ptr
<AuthLua4
>& pdl
, ZoneStatus
& zs
)
590 uint16_t axfr_timeout
= ::arg().asNum("axfr-fetch-timeout");
591 vector
<DNSResourceRecord
> rrs
;
592 AXFRRetriever
retriever(raddr
, domain
, tt
, (laddr
.sin4
.sin_family
== 0) ? nullptr : &laddr
, ((size_t)::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024, axfr_timeout
);
593 Resolver::res_t recs
;
595 bool firstNSEC3
{true};
596 bool soa_received
{false};
597 string logPrefix
= "AXFR-in zone '" + domain
.toLogString() + "', primary '" + raddr
.toString() + "', ";
598 while (retriever
.getChunk(recs
, nullptr, axfr_timeout
)) {
600 g_log
<< Logger::Notice
<< logPrefix
<< "retrieval started" << endl
;
604 for (auto& rec
: recs
) {
605 rec
.qname
.makeUsLowerCase();
606 if (rec
.qtype
.getCode() == QType::OPT
|| rec
.qtype
.getCode() == QType::TSIG
) // ignore EDNS0 & TSIG
609 if (!rec
.qname
.isPartOf(domain
)) {
610 g_log
<< Logger::Warning
<< logPrefix
<< "primary tried to sneak in out-of-zone data '" << rec
.qname
<< "'|" << rec
.qtype
.toString() << ", ignoring" << endl
;
614 vector
<DNSResourceRecord
> out
;
615 if (!pdl
|| !pdl
->axfrfilter(raddr
, domain
, rec
, out
)) {
616 out
.push_back(rec
); // if axfrfilter didn't do anything, we put our record in 'out' ourselves
619 for (auto& rr
: out
) {
620 if (!rr
.qname
.isPartOf(domain
)) {
621 g_log
<< Logger::Error
<< logPrefix
<< "axfrfilter() filter tried to sneak in out-of-zone data '" << rr
.qname
<< "'|" << rr
.qtype
.toString() << ", ignoring" << endl
;
624 if (!processRecordForZS(domain
, firstNSEC3
, rr
, zs
))
626 if (rr
.qtype
.getCode() == QType::SOA
) {
628 continue; // skip the last SOA
630 fillSOAData(rr
.content
, sd
);
631 zs
.soa_serial
= sd
.serial
;
642 void CommunicatorClass::suck(const DNSName
& domain
, const ComboAddress
& remote
, bool force
)
645 auto data
= d_data
.lock();
646 if (data
->d_inprogress
.count(domain
)) {
649 data
->d_inprogress
.insert(domain
);
651 RemoveSentinel
rs(domain
, this); // this removes us from d_inprogress when we go out of scope
653 string logPrefix
= "XFR-in zone: '" + domain
.toLogString() + "', primary: '" + remote
.toString() + "', ";
655 g_log
<< Logger::Notice
<< logPrefix
<< "initiating transfer" << endl
;
656 UeberBackend B
; // fresh UeberBackend
659 di
.backend
= nullptr;
660 bool transaction
= false;
662 DNSSECKeeper
dk(&B
); // reuse our UeberBackend copy for DNSSECKeeper
663 bool wrongDomainKind
= false;
664 // this checks three error conditions & sets wrongDomainKind if we hit the third
665 if (!B
.getDomainInfo(domain
, di
) || !di
.backend
|| (wrongDomainKind
= true, !force
&& !di
.isSecondaryType())) { // di.backend and B are mostly identical
667 g_log
<< Logger::Warning
<< logPrefix
<< "can't determine backend, not configured as secondary" << endl
;
669 g_log
<< Logger::Warning
<< logPrefix
<< "can't determine backend" << endl
;
673 zs
.domain_id
= di
.id
;
676 if (dk
.getTSIGForAccess(domain
, remote
, &tt
.name
)) {
678 if (B
.getTSIGKey(tt
.name
, tt
.algo
, tsigsecret64
)) {
679 if (B64Decode(tsigsecret64
, tt
.secret
)) {
680 g_log
<< Logger::Error
<< logPrefix
<< "unable to Base-64 decode TSIG key '" << tt
.name
<< "' or zone not found" << endl
;
685 g_log
<< Logger::Warning
<< logPrefix
<< "TSIG key '" << tt
.name
<< "' for zone not found" << endl
;
690 unique_ptr
<AuthLua4
> pdl
{nullptr};
691 vector
<string
> scripts
;
692 string script
= ::arg()["lua-axfr-script"];
693 if (B
.getDomainMetadata(domain
, "LUA-AXFR-SCRIPT", scripts
) && !scripts
.empty()) {
694 if (pdns_iequals(scripts
[0], "NONE")) {
701 if (!script
.empty()) {
703 pdl
= make_unique
<AuthLua4
>();
704 pdl
->loadFile(script
);
705 g_log
<< Logger::Info
<< logPrefix
<< "loaded Lua script '" << script
<< "'" << endl
;
707 catch (std::exception
& e
) {
708 g_log
<< Logger::Error
<< logPrefix
<< "failed to load Lua script '" << script
<< "': " << e
.what() << endl
;
713 vector
<string
> localaddr
;
716 if (B
.getDomainMetadata(domain
, "AXFR-SOURCE", localaddr
) && !localaddr
.empty()) {
718 laddr
= ComboAddress(localaddr
[0]);
719 g_log
<< Logger::Info
<< logPrefix
<< "xfr source set to " << localaddr
[0] << endl
;
721 catch (std::exception
& e
) {
722 g_log
<< Logger::Error
<< logPrefix
<< "failed to set xfr source '" << localaddr
[0] << "': " << e
.what() << endl
;
727 if (!pdns::isQueryLocalAddressFamilyEnabled(remote
.sin4
.sin_family
)) {
728 bool isV6
= remote
.sin4
.sin_family
== AF_INET6
;
729 g_log
<< Logger::Warning
<< logPrefix
<< "unable to xfr, address family (IPv" << (isV6
? "6" : "4") << " is not enabled for outgoing traffic (query-local-address)" << endl
;
732 laddr
= pdns::getQueryLocalAddress(remote
.sin4
.sin_family
, 0);
735 bool hadDnssecZone
= false;
736 bool hadPresigned
= false;
737 bool hadNSEC3
= false;
738 NSEC3PARAMRecordContent hadNs3pr
;
739 bool hadNarrow
= false;
741 vector
<DNSResourceRecord
> rrs
;
742 if (dk
.isSecuredZone(domain
, false)) {
743 hadDnssecZone
= true;
744 hadPresigned
= dk
.isPresigned(domain
, false);
745 if (dk
.getNSEC3PARAM(domain
, &zs
.ns3pr
, &zs
.isNarrow
, false)) {
748 hadNarrow
= zs
.isNarrow
;
751 else if (di
.serial
) {
753 B
.getDomainMetadata(domain
, "IXFR", meta
);
754 if (!meta
.empty() && meta
[0] == "1") {
755 logPrefix
= "I" + logPrefix
; // XFR -> IXFR
756 vector
<DNSRecord
> axfr
;
757 g_log
<< Logger::Notice
<< logPrefix
<< "starting IXFR" << endl
;
758 ixfrSuck(domain
, tt
, laddr
, remote
, zs
, &axfr
);
760 g_log
<< Logger::Notice
<< logPrefix
<< "IXFR turned into an AXFR" << endl
;
761 logPrefix
[0] = 'A'; // IXFR -> AXFR
762 bool firstNSEC3
= true;
763 rrs
.reserve(axfr
.size());
764 for (const auto& dr
: axfr
) {
765 auto rr
= DNSResourceRecord::fromWire(dr
);
766 (rr
.qname
+= domain
).makeUsLowerCase();
767 rr
.domain_id
= zs
.domain_id
;
768 if (!processRecordForZS(domain
, firstNSEC3
, rr
, zs
))
770 if (dr
.d_type
== QType::SOA
) {
771 auto sd
= getRR
<SOARecordContent
>(dr
);
772 zs
.soa_serial
= sd
->d_st
.serial
;
778 g_log
<< Logger::Warning
<< logPrefix
<< "got " << zs
.numDeltas
<< " delta" << addS(zs
.numDeltas
) << ", zone committed with serial " << zs
.soa_serial
<< endl
;
779 purgeAuthCaches(domain
.toString() + "$");
786 g_log
<< Logger::Notice
<< logPrefix
<< "starting AXFR" << endl
;
787 rrs
= doAxfr(remote
, domain
, tt
, laddr
, pdl
, zs
);
788 logPrefix
= "A" + logPrefix
; // XFR -> AXFR
789 g_log
<< Logger::Notice
<< logPrefix
<< "retrieval finished" << endl
;
792 if (di
.kind
== DomainInfo::Consumer
) {
793 if (!catalogProcess(di
, rrs
, logPrefix
)) {
794 g_log
<< Logger::Warning
<< logPrefix
<< "Catalog-Zone update failed, only import records" << endl
;
799 zs
.ns3pr
.d_flags
= zs
.optOutFlag
? 1 : 0;
802 if (!zs
.isPresigned
) {
803 DNSSECKeeper::keyset_t keys
= dk
.getKeys(domain
, false);
805 zs
.isDnssecZone
= true;
806 zs
.isNSEC3
= hadNSEC3
;
808 zs
.optOutFlag
= (hadNs3pr
.d_flags
& 1);
809 zs
.isNarrow
= hadNarrow
;
813 if (zs
.isDnssecZone
) {
815 g_log
<< Logger::Debug
<< logPrefix
<< "adding NSEC ordering information" << endl
;
816 else if (!zs
.isNarrow
)
817 g_log
<< Logger::Debug
<< logPrefix
<< "adding NSEC3 hashed ordering information" << endl
;
819 g_log
<< Logger::Debug
<< logPrefix
<< "zone is narrow, only setting 'auth' fields" << endl
;
822 transaction
= di
.backend
->startTransaction(domain
, zs
.domain_id
);
823 g_log
<< Logger::Info
<< logPrefix
<< "storage transaction started" << endl
;
825 // update the presigned flag and NSEC3PARAM
826 if (zs
.isDnssecZone
) {
827 // update presigned if there was a change
828 if (zs
.isPresigned
&& !hadPresigned
) {
829 // zone is now presigned
830 dk
.setPresigned(domain
);
832 else if (hadPresigned
&& !zs
.isPresigned
) {
833 // zone is no longer presigned
834 dk
.unsetPresigned(domain
);
838 // zone is NSEC3, only update if there was a change
839 if (!hadNSEC3
|| (hadNarrow
!= zs
.isNarrow
) || (zs
.ns3pr
.d_algorithm
!= hadNs3pr
.d_algorithm
) || (zs
.ns3pr
.d_flags
!= hadNs3pr
.d_flags
) || (zs
.ns3pr
.d_iterations
!= hadNs3pr
.d_iterations
) || (zs
.ns3pr
.d_salt
!= hadNs3pr
.d_salt
)) {
840 dk
.setNSEC3PARAM(domain
, zs
.ns3pr
, zs
.isNarrow
);
844 // zone is no longer NSEC3
845 dk
.unsetNSEC3PARAM(domain
);
848 else if (hadDnssecZone
) {
849 // zone is no longer signed
852 dk
.unsetPresigned(domain
);
856 dk
.unsetNSEC3PARAM(domain
);
861 uint32_t maxent
= ::arg().asNum("max-ent-entries");
862 DNSName shorter
, ordername
;
864 map
<DNSName
, bool> nonterm
;
866 for (DNSResourceRecord
& rr
: rrs
) {
867 if (!zs
.isPresigned
) {
868 if (rr
.qtype
.getCode() == QType::RRSIG
)
870 if (zs
.isDnssecZone
&& rr
.qtype
.getCode() == QType::DNSKEY
&& !::arg().mustDo("direct-dnskey"))
874 // Figure out auth and ents
880 if (!zs
.qnames
.count(shorter
))
881 rrterm
.insert(shorter
);
883 if (zs
.nsset
.count(shorter
) && rr
.qtype
.getCode() != QType::DS
)
886 if (shorter
== domain
) // stop at apex
888 } while (shorter
.chopOff());
891 if (doent
&& !rrterm
.empty()) {
893 if (!rr
.auth
&& rr
.qtype
.getCode() == QType::NS
) {
895 ordername
= DNSName(toBase32Hex(hashQNameWithSalt(zs
.ns3pr
, rr
.qname
)));
896 auth
= (!zs
.isNSEC3
|| !zs
.optOutFlag
|| zs
.secured
.count(ordername
));
901 for (const auto& nt
: rrterm
) {
902 if (!nonterm
.count(nt
))
903 nonterm
.insert(pair
<DNSName
, bool>(nt
, auth
));
908 if (nonterm
.size() > maxent
) {
909 g_log
<< Logger::Warning
<< logPrefix
<< "zone has too many empty non terminals" << endl
;
915 // RRSIG is always auth, even inside a delegation
916 if (rr
.qtype
.getCode() == QType::RRSIG
)
919 // Add ordername and insert record
920 if (zs
.isDnssecZone
&& rr
.qtype
.getCode() != QType::RRSIG
) {
923 ordername
= DNSName(toBase32Hex(hashQNameWithSalt(zs
.ns3pr
, rr
.qname
)));
924 if (!zs
.isNarrow
&& (rr
.auth
|| (rr
.qtype
.getCode() == QType::NS
&& (!zs
.optOutFlag
|| zs
.secured
.count(ordername
))))) {
925 di
.backend
->feedRecord(rr
, ordername
, true);
928 di
.backend
->feedRecord(rr
, DNSName());
932 if (rr
.auth
|| rr
.qtype
.getCode() == QType::NS
) {
933 ordername
= rr
.qname
.makeRelative(domain
);
934 di
.backend
->feedRecord(rr
, ordername
);
937 di
.backend
->feedRecord(rr
, DNSName());
941 di
.backend
->feedRecord(rr
, DNSName());
944 // Insert empty non-terminals
945 if (doent
&& !nonterm
.empty()) {
947 di
.backend
->feedEnts3(zs
.domain_id
, domain
, nonterm
, zs
.ns3pr
, zs
.isNarrow
);
950 di
.backend
->feedEnts(zs
.domain_id
, nonterm
);
953 di
.backend
->commitTransaction();
955 di
.backend
->setFresh(zs
.domain_id
);
956 purgeAuthCaches(domain
.toString() + "$");
958 g_log
<< Logger::Warning
<< logPrefix
<< "zone committed with serial " << zs
.soa_serial
<< endl
;
960 // Send secondary re-notifications
963 if (B
.getDomainMetadata(domain
, "SLAVE-RENOTIFY", meta
) && !meta
.empty()) {
964 doNotify
= (meta
.front() == "1");
967 doNotify
= (::arg().mustDo("secondary-do-renotify"));
970 notifyDomain(domain
, &B
);
973 catch (DBException
& re
) {
974 g_log
<< Logger::Error
<< logPrefix
<< "unable to feed record: " << re
.reason
<< endl
;
975 if (di
.backend
&& transaction
) {
976 g_log
<< Logger::Info
<< logPrefix
<< "aborting possible open transaction" << endl
;
977 di
.backend
->abortTransaction();
980 catch (const MOADNSException
& mde
) {
981 g_log
<< Logger::Error
<< logPrefix
<< "unable to parse record (MOADNSException): " << mde
.what() << endl
;
982 if (di
.backend
&& transaction
) {
983 g_log
<< Logger::Info
<< logPrefix
<< "aborting possible open transaction" << endl
;
984 di
.backend
->abortTransaction();
987 catch (std::exception
& re
) {
988 g_log
<< Logger::Error
<< logPrefix
<< "unable to xfr zone (std::exception): " << re
.what() << endl
;
989 if (di
.backend
&& transaction
) {
990 g_log
<< Logger::Info
<< logPrefix
<< "aborting possible open transaction" << endl
;
991 di
.backend
->abortTransaction();
994 catch (ResolverException
& re
) {
996 auto data
= d_data
.lock();
997 // The AXFR probably failed due to a problem on the primary server. If SOA-checks against this primary
998 // still succeed, we would constantly try to AXFR the zone. To avoid this, we add the zone to the list of
999 // failed secondary-checks. This will suspend secondary-checks (and subsequent AXFR) for this zone for some time.
1000 uint64_t newCount
= 1;
1001 time_t now
= time(nullptr);
1002 const auto failedEntry
= data
->d_failedSecondaryRefresh
.find(domain
);
1003 if (failedEntry
!= data
->d_failedSecondaryRefresh
.end()) {
1004 newCount
= data
->d_failedSecondaryRefresh
[domain
].first
+ 1;
1006 time_t nextCheck
= now
+ std::min(newCount
* d_tickinterval
, (uint64_t)::arg().asNum("default-ttl"));
1007 data
->d_failedSecondaryRefresh
[domain
] = {newCount
, nextCheck
};
1008 g_log
<< Logger::Warning
<< logPrefix
<< "unable to xfr zone (ResolverException): " << re
.reason
<< " (This was attempt number " << newCount
<< ". Excluding zone from secondary-checks until " << nextCheck
<< ")" << endl
;
1010 if (di
.backend
&& transaction
) {
1011 g_log
<< Logger::Info
<< "aborting possible open transaction" << endl
;
1012 di
.backend
->abortTransaction();
1015 catch (PDNSException
& ae
) {
1016 g_log
<< Logger::Error
<< logPrefix
<< "unable to xfr zone (PDNSException): " << ae
.reason
<< endl
;
1017 if (di
.backend
&& transaction
) {
1018 g_log
<< Logger::Info
<< logPrefix
<< "aborting possible open transaction" << endl
;
1019 di
.backend
->abortTransaction();
1025 struct DomainNotificationInfo
1029 ComboAddress localaddr
;
1030 DNSName tsigkeyname
, tsigalgname
;
1035 struct SecondarySenderReceiver
1037 typedef std::tuple
<DNSName
, ComboAddress
, uint16_t> Identifier
;
1041 uint32_t theirSerial
;
1042 uint32_t theirInception
;
1043 uint32_t theirExpire
;
1046 map
<uint32_t, Answer
> d_freshness
;
1048 void deliverTimeout(const Identifier
& /* i */)
1052 Identifier
send(DomainNotificationInfo
& dni
)
1054 shuffle(dni
.di
.primaries
.begin(), dni
.di
.primaries
.end(), pdns::dns_random_engine());
1056 return {dni
.di
.zone
,
1057 *dni
.di
.primaries
.begin(),
1058 d_resolver
.sendResolve(*dni
.di
.primaries
.begin(),
1063 dni
.dnssecOk
, dni
.tsigkeyname
, dni
.tsigalgname
, dni
.tsigsecret
)};
1065 catch (PDNSException
& e
) {
1066 throw runtime_error("While attempting to query freshness of '" + dni
.di
.zone
.toLogString() + "': " + e
.reason
);
1070 bool receive(Identifier
& id
, Answer
& a
)
1072 return d_resolver
.tryGetSOASerial(&(std::get
<0>(id
)), &(std::get
<1>(id
)), &a
.theirSerial
, &a
.theirInception
, &a
.theirExpire
, &(std::get
<2>(id
)));
1075 void deliverAnswer(const DomainNotificationInfo
& dni
, const Answer
& a
, unsigned int /* usec */)
1077 d_freshness
[dni
.di
.id
] = a
;
1080 Resolver d_resolver
;
1083 void CommunicatorClass::addSecondaryCheckRequest(const DomainInfo
& di
, const ComboAddress
& remote
)
1085 auto data
= d_data
.lock();
1086 DomainInfo ours
= di
;
1087 ours
.backend
= nullptr;
1089 // When adding a check, if the remote addr from which notification was
1090 // received is a primary, clear all other primaries so we can be sure the
1091 // query goes to that one.
1092 for (const auto& primary
: di
.primaries
) {
1093 if (ComboAddress::addressOnlyEqual()(remote
, primary
)) {
1094 ours
.primaries
.clear();
1095 ours
.primaries
.push_back(primary
);
1099 data
->d_tocheck
.erase(di
);
1100 data
->d_tocheck
.insert(ours
);
1101 d_any_sem
.post(); // kick the loop!
1104 void CommunicatorClass::addTryAutoPrimaryRequest(const DNSPacket
& p
)
1106 const DNSPacket
& ours
= p
;
1107 auto data
= d_data
.lock();
1108 if (data
->d_potentialautoprimaries
.insert(ours
).second
) {
1109 d_any_sem
.post(); // kick the loop!
1113 void CommunicatorClass::secondaryRefresh(PacketHandler
* P
)
1115 // not unless we are secondary
1116 if (!::arg().mustDo("secondary"))
1119 UeberBackend
* B
= P
->getBackend();
1120 vector
<DomainInfo
> rdomains
;
1121 vector
<DomainNotificationInfo
> sdomains
;
1122 set
<DNSPacket
, Data::cmp
> trysuperdomains
;
1124 auto data
= d_data
.lock();
1125 set
<DomainInfo
> requeue
;
1126 rdomains
.reserve(data
->d_tocheck
.size());
1127 for (const auto& di
: data
->d_tocheck
) {
1128 if (data
->d_inprogress
.count(di
.zone
)) {
1129 g_log
<< Logger::Debug
<< "Got NOTIFY for " << di
.zone
<< " while AXFR in progress, requeueing SOA check" << endl
;
1133 // We received a NOTIFY for a zone. This means at least one of the zone's primary server is working.
1134 // Therefore we delete the zone from the list of failed secondary-checks to allow immediate checking.
1135 const auto wasFailedDomain
= data
->d_failedSecondaryRefresh
.find(di
.zone
);
1136 if (wasFailedDomain
!= data
->d_failedSecondaryRefresh
.end()) {
1137 g_log
<< Logger::Debug
<< "Got NOTIFY for " << di
.zone
<< ", removing zone from list of failed secondary-checks and going to check SOA serial" << endl
;
1138 data
->d_failedSecondaryRefresh
.erase(di
.zone
);
1141 g_log
<< Logger::Debug
<< "Got NOTIFY for " << di
.zone
<< ", going to check SOA serial" << endl
;
1143 rdomains
.push_back(di
);
1146 data
->d_tocheck
.swap(requeue
);
1148 trysuperdomains
= std::move(data
->d_potentialautoprimaries
);
1149 data
->d_potentialautoprimaries
.clear();
1152 for (const DNSPacket
& dp
: trysuperdomains
) {
1153 // get the TSIG key name
1154 TSIGRecordContent trc
;
1155 DNSName tsigkeyname
;
1156 dp
.getTSIGDetails(&trc
, &tsigkeyname
);
1157 P
->tryAutoPrimarySynchronous(dp
, tsigkeyname
); // FIXME could use some error logging
1159 if (rdomains
.empty()) { // if we have priority domains, check them first
1160 B
->getUnfreshSecondaryInfos(&rdomains
);
1162 sdomains
.reserve(rdomains
.size());
1163 DNSSECKeeper
dk(B
); // NOW HEAR THIS! This DK uses our B backend, so no interleaved access!
1164 bool checkSignatures
= ::arg().mustDo("secondary-check-signature-freshness") && dk
.doesDNSSEC();
1166 auto data
= d_data
.lock();
1167 domains_by_name_t
& nameindex
= boost::multi_index::get
<IDTag
>(data
->d_suckdomains
);
1168 time_t now
= time(nullptr);
1170 for (DomainInfo
& di
: rdomains
) {
1171 const auto failed
= data
->d_failedSecondaryRefresh
.find(di
.zone
);
1172 if (failed
!= data
->d_failedSecondaryRefresh
.end() && now
< failed
->second
.second
) {
1173 // If the domain has failed before and the time before the next check has not expired, skip this domain
1174 g_log
<< Logger::Debug
<< "Zone '" << di
.zone
<< "' is on the list of failed SOA checks. Skipping SOA checks until " << failed
->second
.second
<< endl
;
1177 std::vector
<std::string
> localaddr
;
1179 sr
.domain
= di
.zone
;
1180 if (di
.primaries
.empty()) // secondary domains w/o primaries are ignored
1182 // remove unfresh domains already queued for AXFR, no sense polling them again
1183 sr
.primary
= *di
.primaries
.begin();
1184 if (nameindex
.count(sr
)) { // this does NOT however protect us against AXFRs already in progress!
1187 if (data
->d_inprogress
.count(sr
.domain
)) { // this does
1191 DomainNotificationInfo dni
;
1193 dni
.dnssecOk
= checkSignatures
;
1195 if (dk
.getTSIGForAccess(di
.zone
, sr
.primary
, &dni
.tsigkeyname
)) {
1197 if (!B
->getTSIGKey(dni
.tsigkeyname
, dni
.tsigalgname
, secret64
)) {
1198 g_log
<< Logger::Warning
<< "TSIG key '" << dni
.tsigkeyname
<< "' for domain '" << di
.zone
<< "' not found, can not AXFR." << endl
;
1201 if (B64Decode(secret64
, dni
.tsigsecret
) == -1) {
1202 g_log
<< Logger::Error
<< "Unable to Base-64 decode TSIG key '" << dni
.tsigkeyname
<< "' for domain '" << di
.zone
<< "', can not AXFR." << endl
;
1208 // check for AXFR-SOURCE
1209 if (B
->getDomainMetadata(di
.zone
, "AXFR-SOURCE", localaddr
) && !localaddr
.empty()) {
1211 dni
.localaddr
= ComboAddress(localaddr
[0]);
1212 g_log
<< Logger::Info
<< "Freshness check source (AXFR-SOURCE) for domain '" << di
.zone
<< "' set to " << localaddr
[0] << endl
;
1214 catch (std::exception
& e
) {
1215 g_log
<< Logger::Error
<< "Failed to load freshness check source '" << localaddr
[0] << "' for '" << di
.zone
<< "': " << e
.what() << endl
;
1220 dni
.localaddr
.sin4
.sin_family
= 0;
1223 sdomains
.push_back(std::move(dni
));
1226 if (sdomains
.empty()) {
1227 if (d_secondarieschanged
) {
1228 auto data
= d_data
.lock();
1229 g_log
<< Logger::Info
<< "No new unfresh secondary domains, " << data
->d_suckdomains
.size() << " queued for AXFR already, " << data
->d_inprogress
.size() << " in progress" << endl
;
1231 d_secondarieschanged
= !rdomains
.empty();
1235 auto data
= d_data
.lock();
1236 g_log
<< Logger::Info
<< sdomains
.size() << " secondary domain" << (sdomains
.size() > 1 ? "s" : "") << " need" << (sdomains
.size() > 1 ? "" : "s") << " checking, " << data
->d_suckdomains
.size() << " queued for AXFR" << endl
;
1239 SecondarySenderReceiver ssr
;
1241 Inflighter
<vector
<DomainNotificationInfo
>, SecondarySenderReceiver
> ifl(sdomains
, ssr
);
1243 ifl
.d_maxInFlight
= 200;
1250 catch (std::exception
& e
) {
1251 g_log
<< Logger::Error
<< "While checking domain freshness: " << e
.what() << endl
;
1253 catch (PDNSException
& re
) {
1254 g_log
<< Logger::Error
<< "While checking domain freshness: " << re
.reason
<< endl
;
1258 if (ifl
.getTimeouts()) {
1259 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
;
1262 g_log
<< Logger::Info
<< "Received serial number updates for " << ssr
.d_freshness
.size() << " zone" << addS(ssr
.d_freshness
.size()) << endl
;
1265 time_t now
= time(nullptr);
1266 for (auto& val
: sdomains
) {
1267 DomainInfo
& di(val
.di
);
1268 // If our di comes from packethandler (caused by incoming NOTIFY), di.backend will not be filled out,
1269 // and di.serial will not either.
1270 // Conversely, if our di came from getUnfreshSecondaryInfos, di.backend and di.serial are valid.
1272 // Do not overwrite received DI just to make sure it exists in backend:
1273 // di.primaries should contain the picked primary (as first entry)!
1275 if (!B
->getDomainInfo(di
.zone
, tempdi
, false)) {
1276 g_log
<< Logger::Info
<< "Ignore domain " << di
.zone
<< " since it has been removed from our backend" << endl
;
1279 // Backend for di still doesn't exist and this might cause us to
1280 // SEGFAULT on the setFresh command later on
1281 di
.backend
= tempdi
.backend
;
1284 if (!ssr
.d_freshness
.count(di
.id
)) { // If we don't have an answer for the domain
1285 uint64_t newCount
= 1;
1286 auto data
= d_data
.lock();
1287 const auto failedEntry
= data
->d_failedSecondaryRefresh
.find(di
.zone
);
1288 if (failedEntry
!= data
->d_failedSecondaryRefresh
.end())
1289 newCount
= data
->d_failedSecondaryRefresh
[di
.zone
].first
+ 1;
1290 time_t nextCheck
= now
+ std::min(newCount
* d_tickinterval
, (uint64_t)::arg().asNum("default-ttl"));
1291 data
->d_failedSecondaryRefresh
[di
.zone
] = {newCount
, nextCheck
};
1292 if (newCount
== 1) {
1293 g_log
<< Logger::Warning
<< "Unable to retrieve SOA for " << di
.zone
<< ", this was the first time. NOTE: For every subsequent failed SOA check the domain will be suspended from freshness checks for 'num-errors x " << d_tickinterval
<< " seconds', with a maximum of " << (uint64_t)::arg().asNum("default-ttl") << " seconds. Skipping SOA checks until " << nextCheck
<< endl
;
1295 else if (newCount
% 10 == 0) {
1296 g_log
<< Logger::Notice
<< "Unable to retrieve SOA for " << di
.zone
<< ", this was the " << std::to_string(newCount
) << "th time. Skipping SOA checks until " << nextCheck
<< endl
;
1298 // Make sure we recheck SOA for notifies
1299 if (di
.receivedNotify
) {
1300 di
.backend
->setStale(di
.id
);
1306 auto data
= d_data
.lock();
1307 const auto wasFailedDomain
= data
->d_failedSecondaryRefresh
.find(di
.zone
);
1308 if (wasFailedDomain
!= data
->d_failedSecondaryRefresh
.end())
1309 data
->d_failedSecondaryRefresh
.erase(di
.zone
);
1312 bool hasSOA
= false;
1315 // Use UeberBackend cache for SOA. Cache gets cleared after AXFR/IXFR.
1316 B
->lookup(QType(QType::SOA
), di
.zone
, di
.id
, nullptr);
1318 hasSOA
= B
->get(zr
);
1320 fillSOAData(zr
, sd
);
1328 uint32_t theirserial
= ssr
.d_freshness
[di
.id
].theirSerial
;
1329 uint32_t ourserial
= sd
.serial
;
1330 const ComboAddress remote
= *di
.primaries
.begin();
1332 if (hasSOA
&& rfc1982LessThan(theirserial
, ourserial
) && !::arg().mustDo("axfr-lower-serial")) {
1333 g_log
<< Logger::Warning
<< "Domain '" << di
.zone
<< "' more recent than primary " << remote
.toStringWithPortExcept(53) << ", our serial " << ourserial
<< " > their serial " << theirserial
<< endl
;
1334 di
.backend
->setFresh(di
.id
);
1336 else if (hasSOA
&& theirserial
== ourserial
) {
1337 uint32_t maxExpire
= 0, maxInception
= 0;
1338 if (checkSignatures
&& dk
.isPresigned(di
.zone
)) {
1339 B
->lookup(QType(QType::RRSIG
), di
.zone
, di
.id
); // can't use DK before we are done with this lookup!
1341 while (B
->get(zr
)) {
1342 auto rrsig
= getRR
<RRSIGRecordContent
>(zr
.dr
);
1343 if (rrsig
->d_type
== QType::SOA
) {
1344 maxInception
= std::max(maxInception
, rrsig
->d_siginception
);
1345 maxExpire
= std::max(maxExpire
, rrsig
->d_sigexpire
);
1350 SuckRequest::RequestPriority prio
= SuckRequest::SignaturesRefresh
;
1351 if (di
.receivedNotify
) {
1352 prio
= SuckRequest::Notify
;
1355 if (!maxInception
&& !ssr
.d_freshness
[di
.id
].theirInception
) {
1356 g_log
<< Logger::Info
<< "Domain '" << di
.zone
<< "' is fresh (no DNSSEC), serial is " << ourserial
<< " (checked primary " << remote
.toStringWithPortExcept(53) << ")" << endl
;
1357 di
.backend
->setFresh(di
.id
);
1359 else if (maxInception
== ssr
.d_freshness
[di
.id
].theirInception
&& maxExpire
== ssr
.d_freshness
[di
.id
].theirExpire
) {
1360 g_log
<< Logger::Info
<< "Domain '" << di
.zone
<< "' is fresh and SOA RRSIGs match, serial is " << ourserial
<< " (checked primary " << remote
.toStringWithPortExcept(53) << ")" << endl
;
1361 di
.backend
->setFresh(di
.id
);
1363 else if (maxExpire
>= now
&& !ssr
.d_freshness
[di
.id
].theirInception
) {
1364 g_log
<< Logger::Info
<< "Domain '" << di
.zone
<< "' is fresh, primary " << remote
.toStringWithPortExcept(53) << " is no longer signed but (some) signatures are still valid, serial is " << ourserial
<< endl
;
1365 di
.backend
->setFresh(di
.id
);
1367 else if (maxInception
&& !ssr
.d_freshness
[di
.id
].theirInception
) {
1368 g_log
<< Logger::Notice
<< "Domain '" << di
.zone
<< "' is stale, primary " << remote
.toStringWithPortExcept(53) << " is no longer signed and all signatures have expired, serial is " << ourserial
<< endl
;
1369 addSuckRequest(di
.zone
, remote
, prio
);
1371 else if (dk
.doesDNSSEC() && !maxInception
&& ssr
.d_freshness
[di
.id
].theirInception
) {
1372 g_log
<< Logger::Notice
<< "Domain '" << di
.zone
<< "' is stale, primary " << remote
.toStringWithPortExcept(53) << " has signed, serial is " << ourserial
<< endl
;
1373 addSuckRequest(di
.zone
, remote
, prio
);
1376 g_log
<< Logger::Notice
<< "Domain '" << di
.zone
<< "' is fresh, but RRSIGs differ on primary " << remote
.toStringWithPortExcept(53) << ", so DNSSEC is stale, serial is " << ourserial
<< endl
;
1377 addSuckRequest(di
.zone
, remote
, prio
);
1381 SuckRequest::RequestPriority prio
= SuckRequest::SerialRefresh
;
1382 if (di
.receivedNotify
) {
1383 prio
= SuckRequest::Notify
;
1387 g_log
<< Logger::Notice
<< "Domain '" << di
.zone
<< "' is stale, primary " << remote
.toStringWithPortExcept(53) << " serial " << theirserial
<< ", our serial " << ourserial
<< endl
;
1390 g_log
<< Logger::Notice
<< "Domain '" << di
.zone
<< "' is empty, primary " << remote
.toStringWithPortExcept(53) << " serial " << theirserial
<< endl
;
1392 addSuckRequest(di
.zone
, remote
, prio
);
1397 vector
<pair
<DNSName
, ComboAddress
>> CommunicatorClass::getSuckRequests()
1399 vector
<pair
<DNSName
, ComboAddress
>> ret
;
1400 auto data
= d_data
.lock();
1401 ret
.reserve(data
->d_suckdomains
.size());
1402 for (auto const& d
: data
->d_suckdomains
) {
1403 ret
.emplace_back(d
.domain
, d
.primary
);
1408 size_t CommunicatorClass::getSuckRequestsWaiting()
1410 return d_data
.lock()->d_suckdomains
.size();