]>
Commit | Line | Data |
---|---|---|
3696224d | 1 | /* |
6edbf68a PL |
2 | * This file is part of PowerDNS or dnsdist. |
3 | * Copyright -- PowerDNS.COM B.V. and its contributors | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of version 2 of the GNU General Public License as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | * In addition, for the avoidance of any doubt, permission is granted to | |
10 | * link this program with OpenSSL and to (re)distribute the binaries | |
11 | * produced as the result of such linking. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
21 | */ | |
870a0fe4 AT |
22 | #ifdef HAVE_CONFIG_H |
23 | #include "config.h" | |
24 | #endif | |
38fdb531 | 25 | |
3696224d | 26 | #include "utility.hh" |
d7652f3a BH |
27 | #include "dnssecinfra.hh" |
28 | #include "dnsseckeeper.hh" | |
29 | #include "base32.hh" | |
3696224d BH |
30 | #include <errno.h> |
31 | #include "communicator.hh" | |
32 | #include <set> | |
33 | #include <boost/utility.hpp> | |
34 | #include "dnsbackend.hh" | |
35 | #include "ueberbackend.hh" | |
36 | #include "packethandler.hh" | |
37 | #include "resolver.hh" | |
38 | #include "logger.hh" | |
39 | #include "dns.hh" | |
40 | #include "arguments.hh" | |
38fdb531 | 41 | #include "auth-caches.hh" |
fa8fd4d2 | 42 | |
29b92d6f | 43 | #include "base64.hh" |
3696224d | 44 | #include "inflighter.cc" |
3696224d | 45 | #include "namespaces.hh" |
7108e055 | 46 | #include "common_startup.hh" |
3e7dcee6 | 47 | |
cd189f24 | 48 | #include "ixfr.hh" |
fab71044 | 49 | |
d622042f | 50 | void CommunicatorClass::addSuckRequest(const DNSName &domain, const ComboAddress& master) |
3696224d | 51 | { |
0ddde5fb | 52 | std::lock_guard<std::mutex> l(d_lock); |
3696224d BH |
53 | SuckRequest sr; |
54 | sr.domain = domain; | |
55 | sr.master = master; | |
dbcb3066 | 56 | pair<UniQueue::iterator, bool> res; |
a71bee29 PD |
57 | |
58 | res=d_suckdomains.push_back(sr); | |
dbcb3066 | 59 | if(res.second) { |
7f3d870e | 60 | d_suck_sem.post(); |
dbcb3066 | 61 | } |
3e7dcee6 | 62 | |
3696224d BH |
63 | } |
64 | ||
d3ee36f2 | 65 | struct ZoneStatus |
66 | { | |
67 | bool isDnssecZone{false}; | |
68 | bool isPresigned{false}; | |
69 | bool isNSEC3 {false}; | |
70 | bool optOutFlag {false}; | |
71 | NSEC3PARAMRecordContent ns3pr; | |
72 | ||
73 | bool isNarrow{false}; | |
74 | unsigned int soa_serial{0}; | |
75 | set<DNSName> nsset, qnames, secured; | |
76 | uint32_t domain_id; | |
3e7dcee6 | 77 | int numDeltas{0}; |
d3ee36f2 | 78 | }; |
79 | ||
80 | ||
c2826d2e | 81 | void CommunicatorClass::ixfrSuck(const DNSName &domain, const TSIGTriplet& tt, const ComboAddress& laddr, const ComboAddress& remote, unique_ptr<AuthLua4>& pdl, |
3e7dcee6 | 82 | ZoneStatus& zs, vector<DNSRecord>* axfr) |
3696224d | 83 | { |
cd189f24 | 84 | UeberBackend B; // fresh UeberBackend |
85 | ||
86 | DomainInfo di; | |
87 | di.backend=0; | |
88 | // bool transaction=false; | |
89 | try { | |
90 | DNSSECKeeper dk (&B); // reuse our UeberBackend copy for DNSSECKeeper | |
91 | ||
3729e7f8 | 92 | bool wrongDomainKind = false; |
1fbf6fea | 93 | // this checks three error conditions, and sets wrongDomainKind if we hit the third & had an error |
3729e7f8 | 94 | if(!B.getDomainInfo(domain, di) || !di.backend || (wrongDomainKind = true, di.kind != DomainInfo::Slave)) { // di.backend and B are mostly identical |
95 | if(wrongDomainKind) | |
96 | g_log<<Logger::Error<<"Can't determine backend for domain '"<<domain<<"', not configured as slave"<<endl; | |
97 | else | |
98 | g_log<<Logger::Error<<"Can't determine backend for domain '"<<domain<<"'"<<endl; | |
cd189f24 | 99 | return; |
100 | } | |
cd189f24 | 101 | |
102 | soatimes st; | |
103 | memset(&st, 0, sizeof(st)); | |
d3ee36f2 | 104 | st.serial=di.serial; |
cd189f24 | 105 | |
aaf7799a CH |
106 | DNSRecord drsoa; |
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); | |
3e7dcee6 | 109 | zs.numDeltas=deltas.size(); |
110 | // cout<<"Got "<<deltas.size()<<" deltas from serial "<<di.serial<<", applying.."<<endl; | |
cd189f24 | 111 | |
112 | for(const auto& d : deltas) { | |
3e7dcee6 | 113 | const auto& remove = d.first; |
114 | const auto& add = d.second; | |
115 | // cout<<"Delta sizes: "<<remove.size()<<", "<<add.size()<<endl; | |
cd189f24 | 116 | |
3e7dcee6 | 117 | if(remove.empty()) { // we got passed an AXFR! |
118 | *axfr = add; | |
119 | return; | |
120 | } | |
121 | ||
122 | ||
cd189f24 | 123 | // our hammer is 'replaceRRSet(domain_id, qname, qt, vector<DNSResourceRecord>& rrset) |
3e7dcee6 | 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. | |
cd189f24 | 128 | |
3e7dcee6 | 129 | |
130 | map<pair<DNSName,uint16_t>, pair<vector<DNSRecord>, vector<DNSRecord> > > grouped; | |
131 | ||
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); | |
cd189f24 | 136 | |
137 | di.backend->startTransaction(domain, -1); | |
3e7dcee6 | 138 | for(const auto g : grouped) { |
3e7dcee6 | 139 | vector<DNSRecord> rrset; |
abcd36a1 CH |
140 | { |
141 | DNSZoneRecord zrr; | |
fce7ba57 PD |
142 | di.backend->lookup(QType(g.first.second), g.first.first+domain, di.id); |
143 | while(di.backend->get(zrr)) { | |
668624c8 | 144 | zrr.dr.d_name.makeUsRelative(domain); |
abcd36a1 CH |
145 | rrset.push_back(zrr.dr); |
146 | } | |
3e7dcee6 | 147 | } |
148 | // O(N^2)! | |
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); | |
153 | }), rrset.end()); | |
154 | // the DNSRecord== operator compares on name, type, class and lowercase content representation | |
155 | ||
156 | for(const auto& x : g.second.second) { | |
157 | rrset.push_back(x); | |
158 | } | |
cd189f24 | 159 | |
3e7dcee6 | 160 | vector<DNSResourceRecord> replacement; |
4950b140 CH |
161 | for(const auto& dr : rrset) { |
162 | auto rr = DNSResourceRecord::fromWire(dr); | |
163 | rr.qname += domain; | |
164 | rr.domain_id = di.id; | |
165 | if(dr.d_type == QType::SOA) { | |
3e7dcee6 | 166 | // cout<<"New SOA: "<<x.d_content->getZoneRepresentation()<<endl; |
4950b140 | 167 | auto sr = getRR<SOARecordContent>(dr); |
3e7dcee6 | 168 | zs.soa_serial=sr->d_st.serial; |
169 | } | |
170 | ||
4950b140 | 171 | replacement.push_back(rr); |
3e7dcee6 | 172 | } |
d3ee36f2 | 173 | |
3e7dcee6 | 174 | di.backend->replaceRRSet(di.id, g.first.first+domain, QType(g.first.second), replacement); |
175 | } | |
cd189f24 | 176 | di.backend->commitTransaction(); |
177 | } | |
cd189f24 | 178 | } |
179 | catch(std::exception& p) { | |
e6a9dde5 | 180 | g_log<<Logger::Error<<"Got exception during IXFR: "<<p.what()<<endl; |
3e7dcee6 | 181 | throw; |
cd189f24 | 182 | } |
183 | catch(PDNSException& p) { | |
e6a9dde5 | 184 | g_log<<Logger::Error<<"Got exception during IXFR: "<<p.reason<<endl; |
3e7dcee6 | 185 | throw; |
186 | } | |
187 | } | |
cd189f24 | 188 | |
3e7dcee6 | 189 | |
190 | static bool processRecordForZS(const DNSName& domain, bool& firstNSEC3, DNSResourceRecord& rr, ZoneStatus& zs) | |
191 | { | |
192 | switch(rr.qtype.getCode()) { | |
193 | case QType::NSEC3PARAM: | |
194 | zs.ns3pr = NSEC3PARAMRecordContent(rr.content); | |
195 | zs.isDnssecZone = zs.isNSEC3 = true; | |
196 | zs.isNarrow = false; | |
e4fb8488 | 197 | return false; |
3e7dcee6 | 198 | case QType::NSEC3: { |
199 | NSEC3RecordContent ns3rc(rr.content); | |
200 | if (firstNSEC3) { | |
201 | zs.isDnssecZone = zs.isPresigned = true; | |
202 | firstNSEC3 = false; | |
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; | |
27d4a65b | 206 | if (ns3rc.isSet(QType::NS) && !(rr.qname==domain)) { |
92c6c467 | 207 | DNSName hashPart = rr.qname.makeRelative(domain); |
3e7dcee6 | 208 | zs.secured.insert(hashPart); |
209 | } | |
e4fb8488 | 210 | return false; |
3e7dcee6 | 211 | } |
212 | ||
213 | case QType::NSEC: | |
214 | zs.isDnssecZone = zs.isPresigned = true; | |
e4fb8488 | 215 | return false; |
3e7dcee6 | 216 | |
217 | case QType::NS: | |
218 | if(rr.qname!=domain) | |
219 | zs.nsset.insert(rr.qname); | |
bd9602fa | 220 | break; |
3e7dcee6 | 221 | } |
222 | ||
223 | zs.qnames.insert(rr.qname); | |
224 | ||
225 | rr.domain_id=zs.domain_id; | |
226 | return true; | |
cd189f24 | 227 | } |
228 | ||
d3ee36f2 | 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 | |
238 | */ | |
cd189f24 | 239 | |
c2826d2e | 240 | static vector<DNSResourceRecord> doAxfr(const ComboAddress& raddr, const DNSName& domain, const TSIGTriplet& tt, const ComboAddress& laddr, unique_ptr<AuthLua4>& pdl, ZoneStatus& zs) |
d3ee36f2 | 241 | { |
e3619f57 | 242 | uint16_t axfr_timeout=::arg().asNum("axfr-fetch-timeout"); |
d3ee36f2 | 243 | vector<DNSResourceRecord> rrs; |
e3619f57 | 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); |
d3ee36f2 | 245 | Resolver::res_t recs; |
246 | bool first=true; | |
247 | bool firstNSEC3{true}; | |
248 | bool soa_received {false}; | |
e3619f57 | 249 | while(retriever.getChunk(recs, nullptr, axfr_timeout)) { |
d3ee36f2 | 250 | if(first) { |
e6a9dde5 | 251 | g_log<<Logger::Error<<"AXFR started for '"<<domain<<"'"<<endl; |
d3ee36f2 | 252 | first=false; |
253 | } | |
254 | ||
255 | for(Resolver::res_t::iterator i=recs.begin();i!=recs.end();++i) { | |
92c6c467 | 256 | i->qname.makeUsLowerCase(); |
d3ee36f2 | 257 | if(i->qtype.getCode() == QType::OPT || i->qtype.getCode() == QType::TSIG) // ignore EDNS0 & TSIG |
258 | continue; | |
259 | ||
260 | if(!i->qname.isPartOf(domain)) { | |
e6a9dde5 | 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; |
d3ee36f2 | 262 | continue; |
263 | } | |
264 | ||
265 | vector<DNSResourceRecord> out; | |
266 | if(!pdl || !pdl->axfrfilter(raddr, domain, *i, out)) { | |
3e7dcee6 | 267 | out.push_back(*i); // if axfrfilter didn't do anything, we put our record in 'out' ourselves |
d3ee36f2 | 268 | } |
269 | ||
270 | for(DNSResourceRecord& rr : out) { | |
50b867eb | 271 | if(!rr.qname.isPartOf(domain)) { |
e6a9dde5 | 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; |
50b867eb KM |
273 | continue; |
274 | } | |
3ce97f4a | 275 | if(!processRecordForZS(domain, firstNSEC3, rr, zs)) |
276 | continue; | |
3e7dcee6 | 277 | if(rr.qtype.getCode() == QType::SOA) { |
d3ee36f2 | 278 | if(soa_received) |
279 | continue; //skip the last SOA | |
280 | SOAData sd; | |
281 | fillSOAData(rr.content,sd); | |
282 | zs.soa_serial = sd.serial; | |
283 | soa_received = true; | |
d3ee36f2 | 284 | } |
285 | ||
d3ee36f2 | 286 | rrs.push_back(rr); |
3e7dcee6 | 287 | |
d3ee36f2 | 288 | } |
289 | } | |
290 | } | |
291 | return rrs; | |
292 | } | |
293 | ||
3e7dcee6 | 294 | |
d622042f | 295 | void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote) |
cd189f24 | 296 | { |
3e7dcee6 | 297 | { |
0ddde5fb | 298 | std::lock_guard<std::mutex> l(d_lock); |
3e7dcee6 | 299 | if(d_inprogress.count(domain)) { |
300 | return; | |
301 | } | |
302 | d_inprogress.insert(domain); | |
303 | } | |
304 | RemoveSentinel rs(domain, this); // this removes us from d_inprogress when we go out of scope | |
305 | ||
9b0f144f | 306 | g_log<<Logger::Error<<"Initiating transfer of '"<<domain<<"' from remote '"<<remote<<"'"<<endl; |
295c4a00 | 307 | UeberBackend B; // fresh UeberBackend |
3696224d BH |
308 | |
309 | DomainInfo di; | |
310 | di.backend=0; | |
376ec278 | 311 | bool transaction=false; |
3696224d | 312 | try { |
295c4a00 | 313 | DNSSECKeeper dk (&B); // reuse our UeberBackend copy for DNSSECKeeper |
3729e7f8 | 314 | bool wrongDomainKind = false; |
1fbf6fea | 315 | // this checks three error conditions & sets wrongDomainKind if we hit the third |
3729e7f8 | 316 | if(!B.getDomainInfo(domain, di) || !di.backend || (wrongDomainKind = true, di.kind != DomainInfo::Slave)) { // di.backend and B are mostly identical |
317 | if(wrongDomainKind) | |
318 | g_log<<Logger::Error<<"Can't determine backend for domain '"<<domain<<"', not configured as slave"<<endl; | |
319 | else | |
320 | g_log<<Logger::Error<<"Can't determine backend for domain '"<<domain<<"'"<<endl; | |
3696224d BH |
321 | return; |
322 | } | |
d3ee36f2 | 323 | ZoneStatus zs; |
324 | zs.domain_id=di.id; | |
53568203 | 325 | |
98c9ec39 | 326 | TSIGTriplet tt; |
327 | if(dk.getTSIGForAccess(domain, remote, &tt.name)) { | |
29b92d6f | 328 | string tsigsecret64; |
98c9ec39 | 329 | if(B.getTSIGKey(tt.name, &tt.algo, &tsigsecret64)) { |
330 | if(B64Decode(tsigsecret64, tt.secret)) { | |
e6a9dde5 | 331 | g_log<<Logger::Error<<"Unable to Base-64 decode TSIG key '"<<tt.name<<"' for domain '"<<domain<<"' not found"<<endl; |
98c9ec39 | 332 | return; |
333 | } | |
53568203 | 334 | } else { |
e6a9dde5 | 335 | g_log<<Logger::Error<<"TSIG key '"<<tt.name<<"' for domain '"<<domain<<"' not found"<<endl; |
53568203 | 336 | return; |
a1467662 | 337 | } |
29b92d6f | 338 | } |
53568203 KM |
339 | |
340 | ||
c2826d2e | 341 | unique_ptr<AuthLua4> pdl{nullptr}; |
e23622a7 | 342 | vector<string> scripts; |
86177332 | 343 | string script=::arg()["lua-axfr-script"]; |
295c4a00 | 344 | if(B.getDomainMetadata(domain, "LUA-AXFR-SCRIPT", scripts) && !scripts.empty()) { |
86177332 KM |
345 | if (pdns_iequals(scripts[0], "NONE")) { |
346 | script.clear(); | |
347 | } else { | |
348 | script=scripts[0]; | |
349 | } | |
350 | } | |
351 | if(!script.empty()){ | |
b2aefbf9 | 352 | try { |
c2826d2e | 353 | pdl = make_unique<AuthLua4>(); |
7c99293d | 354 | pdl->loadFile(script); |
e6a9dde5 | 355 | g_log<<Logger::Info<<"Loaded Lua script '"<<script<<"' to edit the incoming AXFR of '"<<domain<<"'"<<endl; |
b2aefbf9 BH |
356 | } |
357 | catch(std::exception& e) { | |
e6a9dde5 | 358 | g_log<<Logger::Error<<"Failed to load Lua editing script '"<<script<<"' for incoming AXFR of '"<<domain<<"': "<<e.what()<<endl; |
b2aefbf9 BH |
359 | return; |
360 | } | |
e23622a7 | 361 | } |
53568203 | 362 | |
fc396d56 BH |
363 | vector<string> localaddr; |
364 | ComboAddress laddr; | |
d622042f | 365 | |
295c4a00 | 366 | if(B.getDomainMetadata(domain, "AXFR-SOURCE", localaddr) && !localaddr.empty()) { |
fc396d56 BH |
367 | try { |
368 | laddr = ComboAddress(localaddr[0]); | |
e6a9dde5 | 369 | g_log<<Logger::Info<<"AXFR source for domain '"<<domain<<"' set to "<<localaddr[0]<<endl; |
fc396d56 BH |
370 | } |
371 | catch(std::exception& e) { | |
e6a9dde5 | 372 | g_log<<Logger::Error<<"Failed to load AXFR source '"<<localaddr[0]<<"' for incoming AXFR of '"<<domain<<"': "<<e.what()<<endl; |
fc396d56 BH |
373 | return; |
374 | } | |
9d36589a | 375 | } else { |
d622042f | 376 | if(remote.sin4.sin_family == AF_INET && !::arg()["query-local-address"].empty()) { |
9d36589a | 377 | laddr = ComboAddress(::arg()["query-local-address"]); |
d622042f | 378 | } else if(remote.sin4.sin_family == AF_INET6 && !::arg()["query-local-address6"].empty()) { |
9d36589a PL |
379 | laddr = ComboAddress(::arg()["query-local-address6"]); |
380 | } else { | |
d622042f | 381 | bool isv6 = remote.sin4.sin_family == AF_INET6; |
e6a9dde5 | 382 | g_log<<Logger::Error<<"Unable to AXFR, destination address is IPv" << (isv6 ? "6" : "4") << ", but query-local-address"<< (isv6 ? "6" : "") << " is unset!"<<endl; |
9d36589a PL |
383 | return; |
384 | } | |
8c949c52 | 385 | } |
fc396d56 | 386 | |
53568203 KM |
387 | bool hadDnssecZone = false; |
388 | bool hadPresigned = false; | |
389 | bool hadNSEC3 = false; | |
d3ee36f2 | 390 | NSEC3PARAMRecordContent hadNs3pr; |
391 | bool hadNarrow=false; | |
392 | ||
edb693f0 | 393 | |
3e7dcee6 | 394 | vector<DNSResourceRecord> rrs; |
53568203 KM |
395 | if(dk.isSecuredZone(domain)) { |
396 | hadDnssecZone=true; | |
397 | hadPresigned=dk.isPresigned(domain); | |
d3ee36f2 | 398 | if (dk.getNSEC3PARAM(domain, &zs.ns3pr, &zs.isNarrow)) { |
53568203 | 399 | hadNSEC3 = true; |
d3ee36f2 | 400 | hadNs3pr = zs.ns3pr; |
401 | hadNarrow = zs.isNarrow; | |
53568203 KM |
402 | } |
403 | } | |
d3ee36f2 | 404 | else if(di.serial) { |
405 | vector<string> meta; | |
406 | B.getDomainMetadata(domain, "IXFR", meta); | |
3e7dcee6 | 407 | if(!meta.empty() && meta[0]=="1") { |
408 | vector<DNSRecord> axfr; | |
9b0f144f | 409 | g_log<<Logger::Warning<<"Starting IXFR of '"<<domain<<"' from remote "<<remote<<endl; |
d622042f | 410 | ixfrSuck(domain, tt, laddr, remote, pdl, zs, &axfr); |
3e7dcee6 | 411 | if(!axfr.empty()) { |
9b0f144f | 412 | g_log<<Logger::Warning<<"IXFR of '"<<domain<<"' from remote '"<<remote<<"' turned into an AXFR"<<endl; |
3e7dcee6 | 413 | bool firstNSEC3=true; |
414 | rrs.reserve(axfr.size()); | |
415 | for(const auto& dr : axfr) { | |
4950b140 | 416 | auto rr = DNSResourceRecord::fromWire(dr); |
92c6c467 | 417 | (rr.qname += domain).makeUsLowerCase(); |
3e7dcee6 | 418 | rr.domain_id = zs.domain_id; |
3ce97f4a | 419 | if(!processRecordForZS(domain, firstNSEC3, rr, zs)) |
420 | continue; | |
3e7dcee6 | 421 | if(dr.d_type == QType::SOA) { |
422 | auto sd = getRR<SOARecordContent>(dr); | |
423 | zs.soa_serial = sd->d_st.serial; | |
424 | } | |
425 | rrs.push_back(rr); | |
426 | } | |
427 | } | |
428 | else { | |
e6a9dde5 | 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; |
1d0995bc | 430 | purgeAuthCaches(domain.toString()+"$"); |
3e7dcee6 | 431 | return; |
432 | } | |
433 | } | |
3696224d | 434 | } |
68fa524b | 435 | |
3e7dcee6 | 436 | if(rrs.empty()) { |
9b0f144f | 437 | g_log<<Logger::Warning<<"Starting AXFR of '"<<domain<<"' from remote "<<remote<<endl; |
d622042f | 438 | rrs = doAxfr(remote, domain, tt, laddr, pdl, zs); |
9b0f144f | 439 | g_log<<Logger::Warning<<"AXFR of '"<<domain<<"' from remote "<<remote<<" done"<<endl; |
3e7dcee6 | 440 | } |
d3ee36f2 | 441 | |
442 | if(zs.isNSEC3) { | |
443 | zs.ns3pr.d_flags = zs.optOutFlag ? 1 : 0; | |
53568203 | 444 | } |
ff473027 | 445 | |
d3ee36f2 | 446 | if(!zs.isPresigned) { |
53568203 KM |
447 | DNSSECKeeper::keyset_t keys = dk.getKeys(domain); |
448 | if(!keys.empty()) { | |
d3ee36f2 | 449 | zs.isDnssecZone = true; |
450 | zs.isNSEC3 = hadNSEC3; | |
451 | zs.ns3pr = hadNs3pr; | |
452 | zs.optOutFlag = (hadNs3pr.d_flags & 1); | |
453 | zs.isNarrow = hadNarrow; | |
53568203 KM |
454 | } |
455 | } | |
456 | ||
d3ee36f2 | 457 | if(zs.isDnssecZone) { |
458 | if(!zs.isNSEC3) | |
e6a9dde5 | 459 | g_log<<Logger::Info<<"Adding NSEC ordering information"<<endl; |
d3ee36f2 | 460 | else if(!zs.isNarrow) |
e6a9dde5 | 461 | g_log<<Logger::Info<<"Adding NSEC3 hashed ordering information for '"<<domain<<"'"<<endl; |
f9cf6d92 | 462 | else |
e6a9dde5 | 463 | g_log<<Logger::Info<<"Erasing NSEC3 ordering since we are narrow, only setting 'auth' fields"<<endl; |
f9cf6d92 KM |
464 | } |
465 | ||
b5baefaf | 466 | |
d3ee36f2 | 467 | transaction=di.backend->startTransaction(domain, zs.domain_id); |
e6a9dde5 | 468 | g_log<<Logger::Error<<"Backend transaction started for '"<<domain<<"' storage"<<endl; |
f9cf6d92 | 469 | |
7f2aa497 | 470 | // update the presigned flag and NSEC3PARAM |
d3ee36f2 | 471 | if (zs.isDnssecZone) { |
53568203 | 472 | // update presigned if there was a change |
d3ee36f2 | 473 | if (zs.isPresigned && !hadPresigned) { |
7f2aa497 KM |
474 | // zone is now presigned |
475 | dk.setPresigned(domain); | |
d3ee36f2 | 476 | } else if (hadPresigned && !zs.isPresigned) { |
53568203 KM |
477 | // zone is no longer presigned |
478 | dk.unsetPresigned(domain); | |
7f2aa497 | 479 | } |
53568203 | 480 | // update NSEC3PARAM |
d3ee36f2 | 481 | if (zs.isNSEC3) { |
53568203 | 482 | // zone is NSEC3, only update if there was a change |
d3ee36f2 | 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); | |
7f2aa497 | 489 | } |
53568203 KM |
490 | } else if (hadNSEC3 ) { |
491 | // zone is no longer NSEC3 | |
492 | dk.unsetNSEC3PARAM(domain); | |
493 | } | |
494 | } else if (hadDnssecZone) { | |
495 | // zone is no longer signed | |
496 | if (hadPresigned) { | |
497 | // remove presigned | |
498 | dk.unsetPresigned(domain); | |
499 | } | |
500 | if (hadNSEC3) { | |
501 | // unset NSEC3PARAM | |
502 | dk.unsetNSEC3PARAM(domain); | |
7f2aa497 | 503 | } |
7f2aa497 KM |
504 | } |
505 | ||
f9cf6d92 | 506 | bool doent=true; |
b5baefaf | 507 | uint32_t maxent = ::arg().asNum("max-ent-entries"); |
c9b43446 | 508 | DNSName shorter, ordername; |
7abbc40f PD |
509 | set<DNSName> rrterm; |
510 | map<DNSName,bool> nonterm; | |
b5baefaf | 511 | |
53568203 | 512 | |
ef7cd021 | 513 | for(DNSResourceRecord& rr : rrs) { |
d3ee36f2 | 514 | if(!zs.isPresigned) { |
53568203 KM |
515 | if (rr.qtype.getCode() == QType::RRSIG) |
516 | continue; | |
d3ee36f2 | 517 | if(zs.isDnssecZone && rr.qtype.getCode() == QType::DNSKEY && !::arg().mustDo("direct-dnskey")) |
53568203 KM |
518 | continue; |
519 | } | |
520 | ||
f9cf6d92 KM |
521 | // Figure out auth and ents |
522 | rr.auth=true; | |
523 | shorter=rr.qname; | |
524 | rrterm.clear(); | |
525 | do { | |
526 | if(doent) { | |
d3ee36f2 | 527 | if (!zs.qnames.count(shorter)) |
f9cf6d92 | 528 | rrterm.insert(shorter); |
d7652f3a | 529 | } |
d3ee36f2 | 530 | if(zs.nsset.count(shorter) && rr.qtype.getCode() != QType::DS) |
f9cf6d92 | 531 | rr.auth=false; |
e5c7239c | 532 | |
e325f20c | 533 | if (shorter==domain) // stop at apex |
f9cf6d92 | 534 | break; |
7abbc40f | 535 | }while(shorter.chopOff()); |
b8adb30d | 536 | |
e5c7239c KM |
537 | // Insert ents |
538 | if(doent && !rrterm.empty()) { | |
539 | bool auth; | |
540 | if (!rr.auth && rr.qtype.getCode() == QType::NS) { | |
d3ee36f2 | 541 | if (zs.isNSEC3) |
c9b43446 KM |
542 | ordername=DNSName(toBase32Hex(hashQNameWithSalt(zs.ns3pr, rr.qname))); |
543 | auth=(!zs.isNSEC3 || !zs.optOutFlag || zs.secured.count(ordername)); | |
e5c7239c KM |
544 | } else |
545 | auth=rr.auth; | |
546 | ||
7abbc40f | 547 | for(const auto &nt: rrterm){ |
e5c7239c | 548 | if (!nonterm.count(nt)) |
7abbc40f | 549 | nonterm.insert(pair<DNSName, bool>(nt, auth)); |
e5c7239c KM |
550 | else if (auth) |
551 | nonterm[nt]=true; | |
552 | } | |
553 | ||
f9cf6d92 | 554 | if(nonterm.size() > maxent) { |
e6a9dde5 | 555 | g_log<<Logger::Error<<"AXFR zone "<<domain<<" has too many empty non terminals."<<endl; |
f9cf6d92 KM |
556 | nonterm.clear(); |
557 | doent=false; | |
27045410 | 558 | } |
d7652f3a | 559 | } |
f9cf6d92 KM |
560 | |
561 | // RRSIG is always auth, even inside a delegation | |
562 | if (rr.qtype.getCode() == QType::RRSIG) | |
563 | rr.auth=true; | |
564 | ||
565 | // Add ordername and insert record | |
d3ee36f2 | 566 | if (zs.isDnssecZone && rr.qtype.getCode() != QType::RRSIG) { |
567 | if (zs.isNSEC3) { | |
f9cf6d92 | 568 | // NSEC3 |
c9b43446 KM |
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))))) { | |
3bb3f561 | 571 | di.backend->feedRecord(rr, ordername, true); |
f9cf6d92 | 572 | } else |
c9b43446 | 573 | di.backend->feedRecord(rr, DNSName()); |
f9cf6d92 KM |
574 | } else { |
575 | // NSEC | |
576 | if (rr.auth || rr.qtype.getCode() == QType::NS) { | |
c9b43446 KM |
577 | ordername=rr.qname.makeRelative(domain); |
578 | di.backend->feedRecord(rr, ordername); | |
f9cf6d92 | 579 | } else |
c9b43446 | 580 | di.backend->feedRecord(rr, DNSName()); |
f9cf6d92 KM |
581 | } |
582 | } else | |
c9b43446 | 583 | di.backend->feedRecord(rr, DNSName()); |
d7652f3a | 584 | } |
b5baefaf | 585 | |
f9cf6d92 KM |
586 | // Insert empty non-terminals |
587 | if(doent && !nonterm.empty()) { | |
d3ee36f2 | 588 | if (zs.isNSEC3) { |
589 | di.backend->feedEnts3(zs.domain_id, domain, nonterm, zs.ns3pr, zs.isNarrow); | |
f9cf6d92 | 590 | } else |
d3ee36f2 | 591 | di.backend->feedEnts(zs.domain_id, nonterm); |
b5baefaf PD |
592 | } |
593 | ||
14b7e03b | 594 | di.backend->commitTransaction(); |
376ec278 | 595 | transaction = false; |
d3ee36f2 | 596 | di.backend->setFresh(zs.domain_id); |
38fdb531 | 597 | purgeAuthCaches(domain.toString()+"$"); |
14b7e03b | 598 | |
e6a9dde5 | 599 | g_log<<Logger::Error<<"AXFR done for '"<<domain<<"', zone committed with serial number "<<zs.soa_serial<<endl; |
7b4e8eed | 600 | |
e8cb07bb | 601 | // Send slave re-notifications |
9efb597d | 602 | bool doNotify; |
7b4e8eed | 603 | vector<string> meta; |
e8cb07bb | 604 | if(B.getDomainMetadata(domain, "SLAVE-RENOTIFY", meta ) && !meta.empty()) { |
9efb597d | 605 | doNotify=(meta.front() == "1"); |
e8cb07bb | 606 | } else { |
9efb597d | 607 | doNotify=(::arg().mustDo("slave-renotify")); |
7b4e8eed | 608 | } |
9efb597d | 609 | if(doNotify) { |
3497eb18 | 610 | notifyDomain(domain, &B); |
e8cb07bb KM |
611 | } |
612 | ||
3696224d BH |
613 | } |
614 | catch(DBException &re) { | |
e6a9dde5 | 615 | g_log<<Logger::Error<<"Unable to feed record during incoming AXFR of '" << domain<<"': "<<re.reason<<endl; |
376ec278 | 616 | if(di.backend && transaction) { |
e6a9dde5 | 617 | g_log<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; |
3696224d BH |
618 | di.backend->abortTransaction(); |
619 | } | |
620 | } | |
16ce7f18 JS |
621 | catch(const MOADNSException &mde) { |
622 | g_log<<Logger::Error<<"Unable to parse record during incoming AXFR of '"<<domain<<"' (MOADNSException): "<<mde.what()<<endl; | |
376ec278 | 623 | if(di.backend && transaction) { |
e6a9dde5 | 624 | g_log<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; |
2714db17 BH |
625 | di.backend->abortTransaction(); |
626 | } | |
627 | } | |
628 | catch(std::exception &re) { | |
e6a9dde5 | 629 | g_log<<Logger::Error<<"Unable to parse record during incoming AXFR of '"<<domain<<"' (std::exception): "<<re.what()<<endl; |
376ec278 | 630 | if(di.backend && transaction) { |
e6a9dde5 | 631 | g_log<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; |
75ccb5b9 BH |
632 | di.backend->abortTransaction(); |
633 | } | |
634 | } | |
3696224d | 635 | catch(ResolverException &re) { |
dd73b264 | 636 | { |
0ddde5fb | 637 | std::lock_guard<std::mutex> l(d_lock); |
dd73b264 KD |
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; | |
649 | } | |
376ec278 | 650 | if(di.backend && transaction) { |
e6a9dde5 | 651 | g_log<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; |
7f450125 PD |
652 | di.backend->abortTransaction(); |
653 | } | |
654 | } | |
3f81d239 | 655 | catch(PDNSException &ae) { |
e6a9dde5 | 656 | g_log<<Logger::Error<<"Unable to AXFR zone '"<<domain<<"' from remote '"<<remote<<"' (PDNSException): "<<ae.reason<<endl; |
376ec278 | 657 | if(di.backend && transaction) { |
e6a9dde5 | 658 | g_log<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; |
3696224d BH |
659 | di.backend->abortTransaction(); |
660 | } | |
661 | } | |
662 | } | |
7597b3cf | 663 | namespace { |
7597b3cf BH |
664 | struct DomainNotificationInfo |
665 | { | |
666 | DomainInfo di; | |
667 | bool dnssecOk; | |
95302209 | 668 | ComboAddress localaddr; |
561434a6 PD |
669 | DNSName tsigkeyname, tsigalgname; |
670 | string tsigsecret; | |
7597b3cf BH |
671 | }; |
672 | } | |
673 | ||
3696224d BH |
674 | |
675 | struct SlaveSenderReceiver | |
676 | { | |
40c9a111 | 677 | typedef std::tuple<DNSName, ComboAddress, uint16_t> Identifier; |
53568203 | 678 | |
54b2edb4 BH |
679 | struct Answer { |
680 | uint32_t theirSerial; | |
681 | uint32_t theirInception; | |
682 | uint32_t theirExpire; | |
683 | }; | |
53568203 | 684 | |
54b2edb4 | 685 | map<uint32_t, Answer> d_freshness; |
53568203 | 686 | |
3696224d BH |
687 | SlaveSenderReceiver() |
688 | { | |
3696224d | 689 | } |
53568203 | 690 | |
c0a5fc34 | 691 | void deliverTimeout(const Identifier& i) |
0c01dd7c BH |
692 | { |
693 | } | |
53568203 | 694 | |
7597b3cf | 695 | Identifier send(DomainNotificationInfo& dni) |
3696224d | 696 | { |
d720eb8a | 697 | shuffle(dni.di.masters.begin(), dni.di.masters.end(), pdns::dns_random_engine()); |
0c01dd7c | 698 | try { |
40c9a111 | 699 | return std::make_tuple(dni.di.zone, |
d622042f | 700 | *dni.di.masters.begin(), |
701 | d_resolver.sendResolve(*dni.di.masters.begin(), | |
40c9a111 RG |
702 | dni.localaddr, |
703 | dni.di.zone, | |
704 | QType::SOA, | |
705 | nullptr, | |
706 | dni.dnssecOk, dni.tsigkeyname, dni.tsigalgname, dni.tsigsecret) | |
707 | ); | |
0c01dd7c | 708 | } |
3f81d239 | 709 | catch(PDNSException& e) { |
86f1af1c | 710 | throw runtime_error("While attempting to query freshness of '"+dni.di.zone.toLogString()+"': "+e.reason); |
0c01dd7c | 711 | } |
3696224d | 712 | } |
68dae32c | 713 | |
3696224d BH |
714 | bool receive(Identifier& id, Answer& a) |
715 | { | |
40c9a111 | 716 | if(d_resolver.tryGetSOASerial(&(std::get<0>(id)), &(std::get<1>(id)), &a.theirSerial, &a.theirInception, &a.theirExpire, &(std::get<2>(id)))) { |
3696224d BH |
717 | return 1; |
718 | } | |
719 | return 0; | |
720 | } | |
68dae32c | 721 | |
7597b3cf | 722 | void deliverAnswer(DomainNotificationInfo& dni, const Answer& a, unsigned int usec) |
3696224d | 723 | { |
7597b3cf | 724 | d_freshness[dni.di.id]=a; |
3696224d | 725 | } |
68dae32c | 726 | |
3696224d | 727 | Resolver d_resolver; |
3696224d BH |
728 | }; |
729 | ||
7f3d870e BH |
730 | void CommunicatorClass::addSlaveCheckRequest(const DomainInfo& di, const ComboAddress& remote) |
731 | { | |
0ddde5fb | 732 | std::lock_guard<std::mutex> l(d_lock); |
90c9a70e BH |
733 | DomainInfo ours = di; |
734 | ours.backend = 0; | |
d381d4a4 OM |
735 | |
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. | |
a9abb26d KM |
739 | for (const auto& master : di.masters) { |
740 | if (ComboAddress::addressOnlyEqual()(remote, master)) { | |
d381d4a4 | 741 | ours.masters.clear(); |
a9abb26d | 742 | ours.masters.push_back(master); |
d381d4a4 OM |
743 | break; |
744 | } | |
745 | } | |
746 | d_tocheck.erase(di); | |
90c9a70e | 747 | d_tocheck.insert(ours); |
7f3d870e BH |
748 | d_any_sem.post(); // kick the loop! |
749 | } | |
750 | ||
c2826d2e | 751 | void CommunicatorClass::addTrySuperMasterRequest(const DNSPacket& p) |
7108e055 | 752 | { |
0ddde5fb | 753 | std::lock_guard<std::mutex> l(d_lock); |
c2826d2e | 754 | DNSPacket ours = p; |
02980dc2 | 755 | if(d_potentialsupermasters.insert(ours).second) |
756 | d_any_sem.post(); // kick the loop! | |
7108e055 PD |
757 | } |
758 | ||
3696224d BH |
759 | void CommunicatorClass::slaveRefresh(PacketHandler *P) |
760 | { | |
56a4f068 AT |
761 | // not unless we are slave |
762 | if (!::arg().mustDo("slave")) return; | |
763 | ||
3971cf53 | 764 | UeberBackend *B=P->getBackend(); |
16e654fa | 765 | vector<DomainInfo> rdomains; |
02980dc2 | 766 | vector<DomainNotificationInfo> sdomains; |
767 | set<DNSPacket, cmp> trysuperdomains; | |
7f3d870e | 768 | { |
0ddde5fb | 769 | std::lock_guard<std::mutex> l(d_lock); |
7d4ac70c | 770 | set<DomainInfo> requeue; |
9bbcf03a | 771 | rdomains.reserve(d_tocheck.size()); |
7d4ac70c PD |
772 | for(const auto& di: d_tocheck) { |
773 | if(d_inprogress.count(di.zone)) { | |
774 | g_log<<Logger::Debug<<"Got NOTIFY for "<<di.zone<<" while AXFR in progress, requeueing SOA check"<<endl; | |
775 | requeue.insert(di); | |
776 | } | |
777 | else { | |
f1a7ff7a KD |
778 | // We received a NOTIFY for a zone. This means at least one of the zone's master server is working. |
779 | // Therefore we delete the zone from the list of failed slave-checks to allow immediate checking. | |
780 | const auto wasFailedDomain = d_failedSlaveRefresh.find(di.zone); | |
781 | if (wasFailedDomain != d_failedSlaveRefresh.end()) { | |
782 | g_log<<Logger::Debug<<"Got NOTIFY for "<<di.zone<<", removing zone from list of failed slave-checks and going to check SOA serial"<<endl; | |
783 | d_failedSlaveRefresh.erase(di.zone); | |
784 | } else { | |
785 | g_log<<Logger::Debug<<"Got NOTIFY for "<<di.zone<<", going to check SOA serial"<<endl; | |
786 | } | |
7d4ac70c PD |
787 | rdomains.push_back(di); |
788 | } | |
789 | } | |
790 | d_tocheck.swap(requeue); | |
02980dc2 | 791 | |
792 | trysuperdomains = d_potentialsupermasters; | |
7108e055 | 793 | d_potentialsupermasters.clear(); |
7f3d870e | 794 | } |
68dae32c | 795 | |
02980dc2 | 796 | for(const DNSPacket& dp : trysuperdomains) { |
7731e32c AT |
797 | // get the TSIG key name |
798 | TSIGRecordContent trc; | |
799 | DNSName tsigkeyname; | |
ea3816cf | 800 | dp.getTSIGDetails(&trc, &tsigkeyname); |
ef2ea4bf | 801 | P->trySuperMasterSynchronous(dp, tsigkeyname); // FIXME could use some error logging |
7108e055 | 802 | } |
3e7dcee6 | 803 | if(rdomains.empty()) { // if we have priority domains, check them first |
7f3d870e | 804 | B->getUnfreshSlaveInfos(&rdomains); |
3e7dcee6 | 805 | } |
9bbcf03a | 806 | sdomains.reserve(rdomains.size()); |
936eb34a | 807 | DNSSECKeeper dk(B); // NOW HEAR THIS! This DK uses our B backend, so no interleaved access! |
dbcb3066 | 808 | { |
0ddde5fb | 809 | std::lock_guard<std::mutex> l(d_lock); |
dbcb3066 | 810 | domains_by_name_t& nameindex=boost::multi_index::get<IDTag>(d_suckdomains); |
b239dfba | 811 | time_t now = time(0); |
dbcb3066 | 812 | |
ef7cd021 | 813 | for(DomainInfo& di : rdomains) { |
b239dfba | 814 | const auto failed = d_failedSlaveRefresh.find(di.zone); |
f1a7ff7a | 815 | if (failed != d_failedSlaveRefresh.end() && now < failed->second.second ) { |
b239dfba | 816 | // If the domain has failed before and the time before the next check has not expired, skip this domain |
f1a7ff7a | 817 | g_log<<Logger::Debug<<"Zone '"<<di.zone<<"' is on the list of failed SOA checks. Skipping SOA checks until "<< failed->second.second<<endl; |
b239dfba | 818 | continue; |
f1a7ff7a | 819 | } |
95302209 | 820 | std::vector<std::string> localaddr; |
dbcb3066 BH |
821 | SuckRequest sr; |
822 | sr.domain=di.zone; | |
823 | if(di.masters.empty()) // slave domains w/o masters are ignored | |
824 | continue; | |
825 | // remove unfresh domains already queued for AXFR, no sense polling them again | |
826 | sr.master=*di.masters.begin(); | |
3e7dcee6 | 827 | if(nameindex.count(sr)) { // this does NOT however protect us against AXFRs already in progress! |
dbcb3066 | 828 | continue; |
a71bee29 | 829 | } |
3e7dcee6 | 830 | if(d_inprogress.count(sr.domain)) // this does |
831 | continue; | |
832 | ||
7597b3cf BH |
833 | DomainNotificationInfo dni; |
834 | dni.di=di; | |
7fa35c07 | 835 | dni.dnssecOk = dk.doesDNSSEC(); |
68dae32c | 836 | |
7597b3cf BH |
837 | if(dk.getTSIGForAccess(di.zone, sr.master, &dni.tsigkeyname)) { |
838 | string secret64; | |
40920298 | 839 | if(!B->getTSIGKey(dni.tsigkeyname, &dni.tsigalgname, &secret64)) { |
e6a9dde5 | 840 | g_log<<Logger::Error<<"TSIG key '"<<dni.tsigkeyname<<"' for domain '"<<di.zone<<"' not found, can not AXFR."<<endl; |
40920298 PL |
841 | continue; |
842 | } | |
843 | if (B64Decode(secret64, dni.tsigsecret) == -1) { | |
e6a9dde5 | 844 | g_log<<Logger::Error<<"Unable to Base-64 decode TSIG key '"<<dni.tsigkeyname<<"' for domain '"<<di.zone<<"', can not AXFR."<<endl; |
40920298 PL |
845 | continue; |
846 | } | |
7597b3cf | 847 | } |
95302209 AT |
848 | |
849 | localaddr.clear(); | |
850 | // check for AXFR-SOURCE | |
851 | if(B->getDomainMetadata(di.zone, "AXFR-SOURCE", localaddr) && !localaddr.empty()) { | |
852 | try { | |
853 | dni.localaddr = ComboAddress(localaddr[0]); | |
e6a9dde5 | 854 | g_log<<Logger::Info<<"Freshness check source (AXFR-SOURCE) for domain '"<<di.zone<<"' set to "<<localaddr[0]<<endl; |
95302209 AT |
855 | } |
856 | catch(std::exception& e) { | |
e6a9dde5 | 857 | g_log<<Logger::Error<<"Failed to load freshness check source '"<<localaddr[0]<<"' for '"<<di.zone<<"': "<<e.what()<<endl; |
95302209 AT |
858 | return; |
859 | } | |
860 | } else { | |
861 | dni.localaddr.sin4.sin_family = 0; | |
862 | } | |
863 | ||
9bbcf03a | 864 | sdomains.push_back(std::move(dni)); |
dbcb3066 | 865 | } |
dbcb3066 | 866 | } |
3696224d BH |
867 | if(sdomains.empty()) |
868 | { | |
dbcb3066 | 869 | if(d_slaveschanged) { |
0ddde5fb | 870 | std::lock_guard<std::mutex> l(d_lock); |
e6a9dde5 | 871 | g_log<<Logger::Warning<<"No new unfresh slave domains, "<<d_suckdomains.size()<<" queued for AXFR already, "<<d_inprogress.size()<<" in progress"<<endl; |
dbcb3066 BH |
872 | } |
873 | d_slaveschanged = !rdomains.empty(); | |
3696224d BH |
874 | return; |
875 | } | |
dbcb3066 | 876 | else { |
0ddde5fb | 877 | std::lock_guard<std::mutex> l(d_lock); |
e6a9dde5 | 878 | g_log<<Logger::Warning<<sdomains.size()<<" slave domain"<<(sdomains.size()>1 ? "s" : "")<<" need"<< |
3696224d | 879 | (sdomains.size()>1 ? "" : "s")<< |
dbcb3066 BH |
880 | " checking, "<<d_suckdomains.size()<<" queued for AXFR"<<endl; |
881 | } | |
68dae32c | 882 | |
3696224d | 883 | SlaveSenderReceiver ssr; |
68dae32c | 884 | |
7597b3cf | 885 | Inflighter<vector<DomainNotificationInfo>, SlaveSenderReceiver> ifl(sdomains, ssr); |
68dae32c | 886 | |
3696224d BH |
887 | ifl.d_maxInFlight = 200; |
888 | ||
889 | for(;;) { | |
890 | try { | |
891 | ifl.run(); | |
892 | break; | |
893 | } | |
dbcb3066 | 894 | catch(std::exception& e) { |
e6a9dde5 | 895 | g_log<<Logger::Error<<"While checking domain freshness: " << e.what()<<endl; |
3696224d | 896 | } |
68dae32c | 897 | catch(PDNSException &re) { |
e6a9dde5 | 898 | g_log<<Logger::Error<<"While checking domain freshness: " << re.reason<<endl; |
3696224d BH |
899 | } |
900 | } | |
e6a9dde5 | 901 | 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; |
16e654fa | 902 | |
7597b3cf | 903 | typedef DomainNotificationInfo val_t; |
b239dfba | 904 | time_t now = time(0); |
ef7cd021 | 905 | for(val_t& val : sdomains) { |
7597b3cf | 906 | DomainInfo& di(val.di); |
d381d4a4 | 907 | DomainInfo tempdi; |
f9fa0e2d | 908 | // might've come from the packethandler |
d381d4a4 OM |
909 | // Please do not overwrite received DI just to make sure it exists in backend. |
910 | if(!di.backend) { | |
911 | if (!B->getDomainInfo(di.zone, tempdi)) { | |
e6a9dde5 | 912 | g_log<<Logger::Warning<<"Ignore domain "<< di.zone<<" since it has been removed from our backend"<<endl; |
232f0877 | 913 | continue; |
d381d4a4 OM |
914 | } |
915 | // Backend for di still doesn't exist and this might cause us to | |
916 | // SEGFAULT on the setFresh command later on | |
917 | di.backend = tempdi.backend; | |
f9fa0e2d | 918 | } |
68dae32c | 919 | |
b239dfba PL |
920 | if(!ssr.d_freshness.count(di.id)) { // If we don't have an answer for the domain |
921 | uint64_t newCount = 1; | |
0ddde5fb | 922 | std::lock_guard<std::mutex> l(d_lock); |
b239dfba PL |
923 | const auto failedEntry = d_failedSlaveRefresh.find(di.zone); |
924 | if (failedEntry != d_failedSlaveRefresh.end()) | |
925 | newCount = d_failedSlaveRefresh[di.zone].first + 1; | |
926 | time_t nextCheck = now + std::min(newCount * d_tickinterval, (uint64_t)::arg().asNum("soa-retry-default")); | |
927 | d_failedSlaveRefresh[di.zone] = {newCount, nextCheck}; | |
f1a7ff7a KD |
928 | if (newCount == 1) { |
929 | g_log<<Logger::Warning<<"Unable to retrieve SOA for "<<di.zone<< | |
930 | ", this was the first time. NOTE: For every subsequent failed SOA check the domain will be suspended from freshness checks for 'num-errors x "<< | |
931 | d_tickinterval<<" seconds', with a maximum of "<<(uint64_t)::arg().asNum("soa-retry-default")<<" seconds. Skipping SOA checks until "<<nextCheck<<endl; | |
932 | } else if (newCount % 10 == 0) { | |
933 | 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; | |
934 | } | |
3696224d | 935 | continue; |
b239dfba PL |
936 | } |
937 | ||
f1a7ff7a | 938 | { |
0ddde5fb | 939 | std::lock_guard<std::mutex> l(d_lock); |
f1a7ff7a KD |
940 | const auto wasFailedDomain = d_failedSlaveRefresh.find(di.zone); |
941 | if (wasFailedDomain != d_failedSlaveRefresh.end()) | |
942 | d_failedSlaveRefresh.erase(di.zone); | |
943 | } | |
b239dfba | 944 | |
b76c5f3a KM |
945 | bool hasSOA = false; |
946 | SOAData sd; | |
947 | try{ | |
948 | hasSOA = B->getSOA(di.zone, sd); | |
949 | } | |
950 | catch(...) {} | |
951 | ||
952 | uint32_t theirserial = ssr.d_freshness[di.id].theirSerial, ourserial = sd.serial; | |
68dae32c | 953 | |
0f76617d | 954 | if(rfc1982LessThan(theirserial, ourserial) && ourserial != 0 && !::arg().mustDo("axfr-lower-serial")) { |
e6a9dde5 | 955 | g_log<<Logger::Error<<"Domain '"<<di.zone<<"' more recent than master, our serial " << ourserial << " > their serial "<< theirserial << endl; |
3696224d BH |
956 | di.backend->setFresh(di.id); |
957 | } | |
b76c5f3a | 958 | else if(hasSOA && theirserial == ourserial) { |
7fa35c07 KM |
959 | uint32_t maxExpire=0, maxInception=0; |
960 | if(dk.isPresigned(di.zone)) { | |
1876dfb1 | 961 | B->lookup(QType(QType::RRSIG), di.zone, di.id); // can't use DK before we are done with this lookup! |
abcd36a1 | 962 | DNSZoneRecord zr; |
abcd36a1 CH |
963 | while(B->get(zr)) { |
964 | auto rrsig = getRR<RRSIGRecordContent>(zr.dr); | |
965 | if(rrsig->d_type == QType::SOA) { | |
966 | maxInception = std::max(maxInception, rrsig->d_siginception); | |
967 | maxExpire = std::max(maxExpire, rrsig->d_sigexpire); | |
54b2edb4 BH |
968 | } |
969 | } | |
7fa35c07 KM |
970 | } |
971 | if(! maxInception && ! ssr.d_freshness[di.id].theirInception) { | |
b3d982f3 | 972 | g_log<<Logger::Info<<"Domain '"<< di.zone<<"' is fresh (no DNSSEC), serial is "<<ourserial<<endl; |
7fa35c07 KM |
973 | di.backend->setFresh(di.id); |
974 | } | |
975 | else if(maxInception == ssr.d_freshness[di.id].theirInception && maxExpire == ssr.d_freshness[di.id].theirExpire) { | |
b3d982f3 | 976 | g_log<<Logger::Info<<"Domain '"<< di.zone<<"' is fresh and SOA RRSIGs match, serial is "<<ourserial<<endl; |
7fa35c07 KM |
977 | di.backend->setFresh(di.id); |
978 | } | |
979 | else if(maxExpire >= now && ! ssr.d_freshness[di.id].theirInception ) { | |
b3d982f3 | 980 | g_log<<Logger::Info<<"Domain '"<< di.zone<<"' is fresh, master is no longer signed but (some) signatures are still vallid, serial is "<<ourserial<<endl; |
7fa35c07 KM |
981 | di.backend->setFresh(di.id); |
982 | } | |
983 | else if(maxInception && ! ssr.d_freshness[di.id].theirInception ) { | |
b3d982f3 | 984 | g_log<<Logger::Warning<<"Domain '"<< di.zone<<"' is stale, master is no longer signed and all signatures have expired, serial is "<<ourserial<<endl; |
7fa35c07 KM |
985 | addSuckRequest(di.zone, *di.masters.begin()); |
986 | } | |
987 | else if(dk.doesDNSSEC() && ! maxInception && ssr.d_freshness[di.id].theirInception) { | |
b3d982f3 | 988 | g_log<<Logger::Warning<<"Domain '"<< di.zone<<"' is stale, master has signed, serial is "<<ourserial<<endl; |
7fa35c07 KM |
989 | addSuckRequest(di.zone, *di.masters.begin()); |
990 | } | |
991 | else { | |
b3d982f3 | 992 | g_log<<Logger::Warning<<"Domain '"<< di.zone<<"' is fresh, but RRSIGs differ, so DNSSEC is stale, serial is "<<ourserial<<endl; |
7fa35c07 | 993 | addSuckRequest(di.zone, *di.masters.begin()); |
54b2edb4 | 994 | } |
3696224d BH |
995 | } |
996 | else { | |
b76c5f3a KM |
997 | if(hasSOA) { |
998 | g_log<<Logger::Warning<<"Domain '"<< di.zone<<"' is stale, master serial "<<theirserial<<", our serial "<< ourserial <<endl; | |
999 | } | |
1000 | else { | |
1001 | g_log<<Logger::Warning<<"Domain '"<< di.zone<<"' is empty, master serial "<<theirserial<<endl; | |
1002 | } | |
d3ee36f2 | 1003 | addSuckRequest(di.zone, *di.masters.begin()); |
3696224d BH |
1004 | } |
1005 | } | |
68dae32c | 1006 | } |