]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/slavecommunicator.cc
add domain metadata documentation
[thirdparty/pdns.git] / pdns / slavecommunicator.cc
CommitLineData
3696224d
BH
1/*
2 PowerDNS Versatile Database Driven Nameserver
d7652f3a 3 Copyright (C) 2002-2011 PowerDNS.COM BV
3696224d
BH
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 as
7 published by the Free Software Foundation;
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17*/
18#include "packetcache.hh"
19#include "utility.hh"
d7652f3a
BH
20#include "dnssecinfra.hh"
21#include "dnsseckeeper.hh"
22#include "base32.hh"
3696224d
BH
23#include <errno.h>
24#include "communicator.hh"
25#include <set>
26#include <boost/utility.hpp>
27#include "dnsbackend.hh"
28#include "ueberbackend.hh"
29#include "packethandler.hh"
30#include "resolver.hh"
31#include "logger.hh"
32#include "dns.hh"
33#include "arguments.hh"
34#include "session.hh"
35#include "packetcache.hh"
36#include <boost/foreach.hpp>
37#include <boost/lexical_cast.hpp>
29b92d6f 38#include "base64.hh"
3696224d 39#include "inflighter.cc"
e23622a7 40#include "lua-pdns-recursor.hh"
3696224d 41#include "namespaces.hh"
e23622a7
BH
42#include <boost/scoped_ptr.hpp>
43using boost::scoped_ptr;
3696224d 44
fab71044
BH
45template<typename T> bool rfc1982LessThan(T a, T b)
46{
47 return ((signed)(a - b)) < 0;
48}
49
3696224d
BH
50void CommunicatorClass::addSuckRequest(const string &domain, const string &master, bool priority)
51{
52 Lock l(&d_lock);
53
54 SuckRequest sr;
55 sr.domain = domain;
56 sr.master = master;
dbcb3066 57 pair<UniQueue::iterator, bool> res;
3696224d 58 if(priority) {
dbcb3066
BH
59 res=d_suckdomains.push_front(sr);
60 }
61 else {
62 res=d_suckdomains.push_back(sr);
3696224d 63 }
3696224d 64
dbcb3066 65 if(res.second) {
7f3d870e 66 d_suck_sem.post();
dbcb3066 67 }
3696224d
BH
68}
69
70void CommunicatorClass::suck(const string &domain,const string &remote)
71{
72 L<<Logger::Error<<"Initiating transfer of '"<<domain<<"' from remote '"<<remote<<"'"<<endl;
73 uint32_t domain_id;
74 PacketHandler P;
75
76 DomainInfo di;
77 di.backend=0;
78 bool first=true;
79 try {
3696224d 80 UeberBackend *B=dynamic_cast<UeberBackend *>(P.getBackend());
d7652f3a
BH
81 NSEC3PARAMRecordContent ns3pr;
82 bool narrow;
83 DNSSECKeeper dk;
84 bool dnssecZone = false;
3c873e66 85 bool haveNSEC3=false;
d3e7090c 86 if(dk.isSecuredZone(domain)) {
d7652f3a 87 dnssecZone=true;
3c873e66 88 haveNSEC3=dk.getNSEC3PARAM(domain, &ns3pr, &narrow);
498e0ffa
BH
89 }
90
91 if(dnssecZone) {
3c873e66 92 if(!haveNSEC3)
498e0ffa
BH
93 L<<Logger::Info<<"Adding NSEC ordering information"<<endl;
94 else if(!narrow)
95 L<<Logger::Info<<"Adding NSEC3 hashed ordering information for '"<<domain<<"'"<<endl;
96 else
97 L<<Logger::Info<<"Erasing NSEC3 ordering since we are narrow, only setting 'auth' fields"<<endl;
98 }
3696224d
BH
99
100 if(!B->getDomainInfo(domain, di) || !di.backend) {
101 L<<Logger::Error<<"Can't determine backend for domain '"<<domain<<"'"<<endl;
102 return;
103 }
104 domain_id=di.id;
105
106 Resolver::res_t recs;
d7652f3a 107 set<string> nsset, qnames;
29b92d6f
BH
108
109 ComboAddress raddr(remote, 53);
110
111 string tsigkeyname, tsigalgorithm, tsigsecret;
16e654fa 112
29b92d6f
BH
113 if(dk.getTSIGForAcces(domain, remote, &tsigkeyname)) {
114 string tsigsecret64;
115 B->getTSIGKey(tsigkeyname, &tsigalgorithm, &tsigsecret64);
116 B64Decode(tsigsecret64, tsigsecret);
117 }
16e654fa 118
e23622a7
BH
119 scoped_ptr<PowerDNSLua> pdl;
120 vector<string> scripts;
121 if(B->getDomainMetadata(domain, "LUA-AXFR-SCRIPT", scripts) && !scripts.empty()) {
b2aefbf9
BH
122 try {
123 pdl.reset(new PowerDNSLua(scripts[0]));
124 L<<Logger::Info<<"Loaded Lua script '"<<scripts[0]<<"' to edit the incoming AXFR of '"<<domain<<"'"<<endl;
125 }
126 catch(std::exception& e) {
127 L<<Logger::Error<<"Failed to load Lua editing script '"<<scripts[0]<<"' for incoming AXFR of '"<<domain<<"': "<<e.what()<<endl;
128 return;
129 }
e23622a7 130 }
29b92d6f
BH
131 AXFRRetriever retriever(raddr, domain.c_str(), tsigkeyname, tsigalgorithm, tsigsecret);
132
0c01dd7c 133 while(retriever.getChunk(recs)) {
3696224d 134 if(first) {
4957a608
BH
135 L<<Logger::Error<<"AXFR started for '"<<domain<<"', transaction started"<<endl;
136 di.backend->startTransaction(domain, domain_id);
137 first=false;
3696224d 138 }
d7652f3a 139
3696224d 140 for(Resolver::res_t::iterator i=recs.begin();i!=recs.end();++i) {
25aeec36
BH
141 if(i->qtype.getCode() == QType::OPT) // ignore EDNS0
142 continue;
81c43517
BH
143
144 // we generate NSEC, NSEC3, NSEC3PARAM (sorry Olafur) on the fly, this could only confuse things
145 if(dnssecZone && (i->qtype.getCode() == QType::NSEC || i->qtype.getCode() == QType::NSEC3 ||
146 i->qtype.getCode() == QType::NSEC3PARAM))
147 continue;
148
4957a608 149 if(!endsOn(i->qname, domain)) {
25aeec36 150 L<<Logger::Error<<"Remote "<<remote<<" tried to sneak in out-of-zone data '"<<i->qname<<"'|"<<i->qtype.getName()<<" during AXFR of zone '"<<domain<<"', ignoring"<<endl;
4957a608
BH
151 continue;
152 }
498e0ffa
BH
153
154 if(i->qtype.getCode() == QType::NS && !pdns_iequals(i->qname, domain))
155 nsset.insert(i->qname);
81c43517
BH
156 if(i->qtype.getCode() != QType::RRSIG) // this excludes us hashing RRSIGs for NSEC(3)
157 qnames.insert(i->qname);
498e0ffa 158
4957a608 159 i->domain_id=domain_id;
7fefa73a
BH
160#if 0
161 if(i->qtype.getCode()>=60000)
4957a608 162 throw DBException("Database can't store unknown record type "+lexical_cast<string>(i->qtype.getCode()-1024));
7fefa73a 163#endif
e23622a7
BH
164 vector<DNSResourceRecord> out;
165 if(pdl && pdl->axfrfilter(raddr, domain, *i, out)) {
166 BOOST_FOREACH(const DNSResourceRecord& rr, out) {
167 di.backend->feedRecord(rr);
168 }
169 }
170 else {
171 di.backend->feedRecord(*i);
172 }
3696224d
BH
173 }
174 }
498e0ffa
BH
175
176 string hashed;
177 BOOST_FOREACH(const string& qname, qnames)
178 {
179 string shorter(qname);
180 bool auth=true;
181 do {
182 if(nsset.count(shorter)) {
183 auth=false;
184 break;
185 }
186 }while(chopOff(shorter));
d7652f3a 187
498e0ffa
BH
188 if(dnssecZone && !haveNSEC3) // NSEC
189 di.backend->updateDNSSECOrderAndAuth(domain_id, domain, qname, auth);
190 else {
191 if(dnssecZone && !narrow) {
192 hashed=toLower(toBase32Hex(hashQNameWithSalt(ns3pr.d_iterations, ns3pr.d_salt, qname)));
d7652f3a 193 }
498e0ffa 194 di.backend->updateDNSSECOrderAndAuthAbsolute(domain_id, qname, hashed, auth); // this should always be done
d7652f3a
BH
195 }
196 }
498e0ffa 197
3696224d
BH
198 di.backend->commitTransaction();
199 di.backend->setFresh(domain_id);
200 L<<Logger::Error<<"AXFR done for '"<<domain<<"', zone committed"<<endl;
8de9c054
BH
201 if(::arg().mustDo("slave-renotify"))
202 notifyDomain(domain);
3696224d
BH
203 }
204 catch(DBException &re) {
205 L<<Logger::Error<<"Unable to feed record during incoming AXFR of '"+domain+"': "<<re.reason<<endl;
206 if(di.backend && !first) {
207 L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl;
208 di.backend->abortTransaction();
209 }
210 }
75ccb5b9 211 catch(MOADNSException &re) {
2714db17
BH
212 L<<Logger::Error<<"Unable to parse record during incoming AXFR of '"+domain+"' (MOADNSException): "<<re.what()<<endl;
213 if(di.backend && !first) {
214 L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl;
215 di.backend->abortTransaction();
216 }
217 }
218 catch(std::exception &re) {
219 L<<Logger::Error<<"Unable to parse record during incoming AXFR of '"+domain+"' (std::exception): "<<re.what()<<endl;
75ccb5b9
BH
220 if(di.backend && !first) {
221 L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl;
222 di.backend->abortTransaction();
223 }
224 }
3696224d 225 catch(ResolverException &re) {
2714db17 226 L<<Logger::Error<<"Unable to AXFR zone '"+domain+"' from remote '"<<remote<<"' (resolver): "<<re.reason<<endl;
3696224d
BH
227 if(di.backend && !first) {
228 L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl;
229 di.backend->abortTransaction();
230 }
231 }
232}
233struct QueryInfo
234 {
235 struct timeval query_ttd;
236 uint16_t id;
237 };
238
239struct SlaveSenderReceiver
240{
241 typedef pair<string, uint16_t> Identifier;
3696224d 242
54b2edb4
BH
243 struct Answer {
244 uint32_t theirSerial;
245 uint32_t theirInception;
246 uint32_t theirExpire;
247 };
248
249 map<uint32_t, Answer> d_freshness;
3696224d
BH
250
251 SlaveSenderReceiver()
252 {
3696224d
BH
253 }
254
c0a5fc34 255 void deliverTimeout(const Identifier& i)
0c01dd7c
BH
256 {
257 }
c0a5fc34 258
16e654fa 259 Identifier send(pair<DomainInfo, bool>& dipair)
3696224d 260 {
16e654fa 261 random_shuffle(dipair.first.masters.begin(), dipair.first.masters.end());
0c01dd7c 262 try {
16e654fa
BH
263 ComboAddress remote(*dipair.first.masters.begin());
264 return make_pair(dipair.first.zone, d_resolver.sendResolve(ComboAddress(*dipair.first.masters.begin(), 53), dipair.first.zone.c_str(), QType::SOA, dipair.second));
0c01dd7c
BH
265 }
266 catch(AhuException& e) {
16e654fa 267 throw runtime_error("While attempting to query freshness of '"+dipair.first.zone+"': "+e.reason);
0c01dd7c 268 }
3696224d
BH
269 }
270
271 bool receive(Identifier& id, Answer& a)
272 {
54b2edb4 273 if(d_resolver.tryGetSOASerial(&id.first, &a.theirSerial, &a.theirInception, &a.theirExpire, &id.second)) {
3696224d
BH
274 return 1;
275 }
276 return 0;
277 }
278
16e654fa 279 void deliverAnswer(pair<DomainInfo, bool>& i, const Answer& a, unsigned int usec)
3696224d 280 {
16e654fa 281 d_freshness[i.first.id]=a;
3696224d
BH
282 }
283
284 Resolver d_resolver;
3696224d
BH
285};
286
7f3d870e
BH
287void CommunicatorClass::addSlaveCheckRequest(const DomainInfo& di, const ComboAddress& remote)
288{
289 Lock l(&d_lock);
290 d_tocheck.insert(di);
291 d_any_sem.post(); // kick the loop!
292}
293
3696224d
BH
294void CommunicatorClass::slaveRefresh(PacketHandler *P)
295{
296 UeberBackend *B=dynamic_cast<UeberBackend *>(P->getBackend());
16e654fa 297 vector<DomainInfo> rdomains;
7f3d870e
BH
298 vector<pair<DomainInfo, bool> > sdomains; // the bool is for 'presigned'
299
300 {
301 Lock l(&d_lock);
302 rdomains.insert(rdomains.end(), d_tocheck.begin(), d_tocheck.end());
303 d_tocheck.clear();
304 }
305
306 if(rdomains.empty()) // if we have priority domains, check them first
307 B->getUnfreshSlaveInfos(&rdomains);
308
16e654fa 309 DNSSECKeeper dk;
dbcb3066
BH
310 {
311 Lock l(&d_lock);
312 typedef UniQueue::index<IDTag>::type domains_by_name_t;
313 domains_by_name_t& nameindex=boost::multi_index::get<IDTag>(d_suckdomains);
314
dbcb3066
BH
315 BOOST_FOREACH(DomainInfo& di, rdomains) {
316 SuckRequest sr;
317 sr.domain=di.zone;
318 if(di.masters.empty()) // slave domains w/o masters are ignored
319 continue;
320 // remove unfresh domains already queued for AXFR, no sense polling them again
321 sr.master=*di.masters.begin();
322 if(nameindex.count(sr))
323 continue;
16e654fa
BH
324
325 sdomains.push_back(make_pair(di, dk.isPresigned(di.zone)));
dbcb3066 326 }
dbcb3066
BH
327 }
328
3696224d
BH
329 if(sdomains.empty())
330 {
dbcb3066
BH
331 if(d_slaveschanged) {
332 Lock l(&d_lock);
333 L<<Logger::Warning<<"No new unfresh slave domains, "<<d_suckdomains.size()<<" queued for AXFR already"<<endl;
334 }
335 d_slaveschanged = !rdomains.empty();
3696224d
BH
336 return;
337 }
dbcb3066
BH
338 else {
339 Lock l(&d_lock);
3696224d
BH
340 L<<Logger::Warning<<sdomains.size()<<" slave domain"<<(sdomains.size()>1 ? "s" : "")<<" need"<<
341 (sdomains.size()>1 ? "" : "s")<<
dbcb3066
BH
342 " checking, "<<d_suckdomains.size()<<" queued for AXFR"<<endl;
343 }
3696224d
BH
344
345 SlaveSenderReceiver ssr;
16e654fa 346 Inflighter<vector<pair<DomainInfo, bool> >, SlaveSenderReceiver> ifl(sdomains, ssr);
3696224d
BH
347
348 ifl.d_maxInFlight = 200;
349
350 for(;;) {
351 try {
352 ifl.run();
353 break;
354 }
dbcb3066 355 catch(std::exception& e) {
3696224d
BH
356 L<<Logger::Error<<"While checking domain freshness: " << e.what()<<endl;
357 }
358 catch(AhuException &re) {
359 L<<Logger::Error<<"While checking domain freshness: " << re.reason<<endl;
360 }
361 }
0c01dd7c 362 L<<Logger::Warning<<"Received serial number updates for "<<ssr.d_freshness.size()<<" zones, had "<<ifl.getTimeouts()<<" timeouts"<<endl;
16e654fa
BH
363
364 typedef pair<DomainInfo, bool> val_t;
365 BOOST_FOREACH(val_t& val, sdomains) {
366 DomainInfo& di(val.first);
54b2edb4 367 if(!ssr.d_freshness.count(di.id))
3696224d 368 continue;
54b2edb4 369 uint32_t theirserial = ssr.d_freshness[di.id].theirSerial, ourserial = di.serial;
3696224d 370
fab71044 371 if(rfc1982LessThan(theirserial, ourserial)) {
3696224d
BH
372 L<<Logger::Error<<"Domain "<<di.zone<<" more recent than master, our serial " << ourserial << " > their serial "<< theirserial << endl;
373 di.backend->setFresh(di.id);
374 }
375 else if(theirserial == ourserial) {
54b2edb4
BH
376 if(!dk.isPresigned(di.zone)) {
377 L<<Logger::Warning<<"Domain "<< di.zone<<" is fresh (not presigned, no RRSIG check)"<<endl;
378 di.backend->setFresh(di.id);
379 }
380 else {
381 B->lookup(QType(QType::RRSIG), di.zone);
382 DNSResourceRecord rr;
383 uint32_t maxExpire=0, maxInception=0;
384 while(B->get(rr)) {
385 RRSIGRecordContent rrc(rr.content);
386 if(rrc.d_type == QType::SOA) {
387 maxInception = std::max(maxInception, rrc.d_siginception);
388 maxExpire = std::max(maxExpire, rrc.d_sigexpire);
389 }
390 }
391 if(maxInception == ssr.d_freshness[di.id].theirInception && maxExpire == ssr.d_freshness[di.id].theirExpire) {
392 L<<Logger::Warning<<"Domain "<< di.zone<<" is fresh and apex RRSIGs match"<<endl;
393 di.backend->setFresh(di.id);
394 }
395 else {
396 L<<Logger::Warning<<"Domain "<< di.zone<<" is fresh, but RRSIGS differ, so DNSSEC stale"<<endl;
397 addSuckRequest(di.zone, *di.masters.begin());
398 }
399 }
3696224d
BH
400 }
401 else {
402 L<<Logger::Warning<<"Domain "<< di.zone<<" is stale, master serial "<<theirserial<<", our serial "<< ourserial <<endl;
403 addSuckRequest(di.zone, *di.masters.begin());
3696224d
BH
404 }
405 }
3696224d
BH
406}
407