]>
Commit | Line | Data |
---|---|---|
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> |
43 | using boost::scoped_ptr; | |
3696224d | 44 | |
fab71044 BH |
45 | template<typename T> bool rfc1982LessThan(T a, T b) |
46 | { | |
47 | return ((signed)(a - b)) < 0; | |
48 | } | |
49 | ||
3696224d BH |
50 | void 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 | ||
70 | void 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 | } | |
233 | struct QueryInfo | |
234 | { | |
235 | struct timeval query_ttd; | |
236 | uint16_t id; | |
237 | }; | |
238 | ||
239 | struct 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 |
287 | void 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 |
294 | void 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 |