]>
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 BH |
39 | #include "inflighter.cc" |
40 | ||
41 | #include "namespaces.hh" | |
42 | ||
43 | void CommunicatorClass::addSuckRequest(const string &domain, const string &master, bool priority) | |
44 | { | |
45 | Lock l(&d_lock); | |
46 | ||
47 | SuckRequest sr; | |
48 | sr.domain = domain; | |
49 | sr.master = master; | |
dbcb3066 | 50 | pair<UniQueue::iterator, bool> res; |
3696224d | 51 | if(priority) { |
dbcb3066 BH |
52 | res=d_suckdomains.push_front(sr); |
53 | } | |
54 | else { | |
55 | res=d_suckdomains.push_back(sr); | |
3696224d | 56 | } |
3696224d | 57 | |
dbcb3066 | 58 | if(res.second) { |
3696224d | 59 | d_suck_sem.post(); |
dbcb3066 | 60 | } |
3696224d BH |
61 | } |
62 | ||
63 | void CommunicatorClass::suck(const string &domain,const string &remote) | |
64 | { | |
65 | L<<Logger::Error<<"Initiating transfer of '"<<domain<<"' from remote '"<<remote<<"'"<<endl; | |
66 | uint32_t domain_id; | |
67 | PacketHandler P; | |
68 | ||
69 | DomainInfo di; | |
70 | di.backend=0; | |
71 | bool first=true; | |
72 | try { | |
3696224d | 73 | UeberBackend *B=dynamic_cast<UeberBackend *>(P.getBackend()); |
d7652f3a BH |
74 | NSEC3PARAMRecordContent ns3pr; |
75 | bool narrow; | |
76 | DNSSECKeeper dk; | |
77 | bool dnssecZone = false; | |
3c873e66 | 78 | bool haveNSEC3=false; |
d3e7090c | 79 | if(dk.isSecuredZone(domain)) { |
d7652f3a | 80 | dnssecZone=true; |
3c873e66 | 81 | haveNSEC3=dk.getNSEC3PARAM(domain, &ns3pr, &narrow); |
498e0ffa BH |
82 | } |
83 | ||
84 | if(dnssecZone) { | |
3c873e66 | 85 | if(!haveNSEC3) |
498e0ffa BH |
86 | L<<Logger::Info<<"Adding NSEC ordering information"<<endl; |
87 | else if(!narrow) | |
88 | L<<Logger::Info<<"Adding NSEC3 hashed ordering information for '"<<domain<<"'"<<endl; | |
89 | else | |
90 | L<<Logger::Info<<"Erasing NSEC3 ordering since we are narrow, only setting 'auth' fields"<<endl; | |
91 | } | |
3696224d BH |
92 | |
93 | if(!B->getDomainInfo(domain, di) || !di.backend) { | |
94 | L<<Logger::Error<<"Can't determine backend for domain '"<<domain<<"'"<<endl; | |
95 | return; | |
96 | } | |
97 | domain_id=di.id; | |
98 | ||
99 | Resolver::res_t recs; | |
d7652f3a | 100 | set<string> nsset, qnames; |
29b92d6f BH |
101 | |
102 | ComboAddress raddr(remote, 53); | |
103 | ||
104 | string tsigkeyname, tsigalgorithm, tsigsecret; | |
16e654fa | 105 | |
29b92d6f BH |
106 | if(dk.getTSIGForAcces(domain, remote, &tsigkeyname)) { |
107 | string tsigsecret64; | |
108 | B->getTSIGKey(tsigkeyname, &tsigalgorithm, &tsigsecret64); | |
109 | B64Decode(tsigsecret64, tsigsecret); | |
110 | } | |
16e654fa | 111 | |
29b92d6f BH |
112 | AXFRRetriever retriever(raddr, domain.c_str(), tsigkeyname, tsigalgorithm, tsigsecret); |
113 | ||
0c01dd7c | 114 | while(retriever.getChunk(recs)) { |
3696224d | 115 | if(first) { |
4957a608 BH |
116 | L<<Logger::Error<<"AXFR started for '"<<domain<<"', transaction started"<<endl; |
117 | di.backend->startTransaction(domain, domain_id); | |
118 | first=false; | |
3696224d | 119 | } |
d7652f3a | 120 | |
3696224d | 121 | for(Resolver::res_t::iterator i=recs.begin();i!=recs.end();++i) { |
25aeec36 BH |
122 | if(i->qtype.getCode() == QType::OPT) // ignore EDNS0 |
123 | continue; | |
81c43517 BH |
124 | |
125 | // we generate NSEC, NSEC3, NSEC3PARAM (sorry Olafur) on the fly, this could only confuse things | |
126 | if(dnssecZone && (i->qtype.getCode() == QType::NSEC || i->qtype.getCode() == QType::NSEC3 || | |
127 | i->qtype.getCode() == QType::NSEC3PARAM)) | |
128 | continue; | |
129 | ||
4957a608 | 130 | if(!endsOn(i->qname, domain)) { |
25aeec36 | 131 | 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 |
132 | continue; |
133 | } | |
498e0ffa BH |
134 | |
135 | if(i->qtype.getCode() == QType::NS && !pdns_iequals(i->qname, domain)) | |
136 | nsset.insert(i->qname); | |
81c43517 BH |
137 | if(i->qtype.getCode() != QType::RRSIG) // this excludes us hashing RRSIGs for NSEC(3) |
138 | qnames.insert(i->qname); | |
498e0ffa | 139 | |
4957a608 | 140 | i->domain_id=domain_id; |
7fefa73a BH |
141 | #if 0 |
142 | if(i->qtype.getCode()>=60000) | |
4957a608 | 143 | throw DBException("Database can't store unknown record type "+lexical_cast<string>(i->qtype.getCode()-1024)); |
7fefa73a | 144 | #endif |
4957a608 | 145 | di.backend->feedRecord(*i); |
3696224d BH |
146 | } |
147 | } | |
498e0ffa BH |
148 | |
149 | string hashed; | |
150 | BOOST_FOREACH(const string& qname, qnames) | |
151 | { | |
152 | string shorter(qname); | |
153 | bool auth=true; | |
154 | do { | |
155 | if(nsset.count(shorter)) { | |
156 | auth=false; | |
157 | break; | |
158 | } | |
159 | }while(chopOff(shorter)); | |
d7652f3a | 160 | |
498e0ffa BH |
161 | if(dnssecZone && !haveNSEC3) // NSEC |
162 | di.backend->updateDNSSECOrderAndAuth(domain_id, domain, qname, auth); | |
163 | else { | |
164 | if(dnssecZone && !narrow) { | |
165 | hashed=toLower(toBase32Hex(hashQNameWithSalt(ns3pr.d_iterations, ns3pr.d_salt, qname))); | |
d7652f3a | 166 | } |
498e0ffa | 167 | di.backend->updateDNSSECOrderAndAuthAbsolute(domain_id, qname, hashed, auth); // this should always be done |
d7652f3a BH |
168 | } |
169 | } | |
498e0ffa | 170 | |
3696224d BH |
171 | di.backend->commitTransaction(); |
172 | di.backend->setFresh(domain_id); | |
173 | L<<Logger::Error<<"AXFR done for '"<<domain<<"', zone committed"<<endl; | |
8de9c054 BH |
174 | if(::arg().mustDo("slave-renotify")) |
175 | notifyDomain(domain); | |
3696224d BH |
176 | } |
177 | catch(DBException &re) { | |
178 | L<<Logger::Error<<"Unable to feed record during incoming AXFR of '"+domain+"': "<<re.reason<<endl; | |
179 | if(di.backend && !first) { | |
180 | L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; | |
181 | di.backend->abortTransaction(); | |
182 | } | |
183 | } | |
184 | catch(ResolverException &re) { | |
185 | L<<Logger::Error<<"Unable to AXFR zone '"+domain+"' from remote '"<<remote<<"': "<<re.reason<<endl; | |
186 | if(di.backend && !first) { | |
187 | L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; | |
188 | di.backend->abortTransaction(); | |
189 | } | |
190 | } | |
191 | } | |
192 | struct QueryInfo | |
193 | { | |
194 | struct timeval query_ttd; | |
195 | uint16_t id; | |
196 | }; | |
197 | ||
198 | struct SlaveSenderReceiver | |
199 | { | |
200 | typedef pair<string, uint16_t> Identifier; | |
3696224d | 201 | |
54b2edb4 BH |
202 | struct Answer { |
203 | uint32_t theirSerial; | |
204 | uint32_t theirInception; | |
205 | uint32_t theirExpire; | |
206 | }; | |
207 | ||
208 | map<uint32_t, Answer> d_freshness; | |
3696224d BH |
209 | |
210 | SlaveSenderReceiver() | |
211 | { | |
3696224d BH |
212 | } |
213 | ||
c0a5fc34 | 214 | void deliverTimeout(const Identifier& i) |
0c01dd7c BH |
215 | { |
216 | } | |
c0a5fc34 | 217 | |
16e654fa | 218 | Identifier send(pair<DomainInfo, bool>& dipair) |
3696224d | 219 | { |
16e654fa | 220 | random_shuffle(dipair.first.masters.begin(), dipair.first.masters.end()); |
0c01dd7c | 221 | try { |
16e654fa BH |
222 | ComboAddress remote(*dipair.first.masters.begin()); |
223 | 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 |
224 | } |
225 | catch(AhuException& e) { | |
16e654fa | 226 | throw runtime_error("While attempting to query freshness of '"+dipair.first.zone+"': "+e.reason); |
0c01dd7c | 227 | } |
3696224d BH |
228 | } |
229 | ||
230 | bool receive(Identifier& id, Answer& a) | |
231 | { | |
54b2edb4 | 232 | if(d_resolver.tryGetSOASerial(&id.first, &a.theirSerial, &a.theirInception, &a.theirExpire, &id.second)) { |
3696224d BH |
233 | return 1; |
234 | } | |
235 | return 0; | |
236 | } | |
237 | ||
16e654fa | 238 | void deliverAnswer(pair<DomainInfo, bool>& i, const Answer& a, unsigned int usec) |
3696224d | 239 | { |
16e654fa | 240 | d_freshness[i.first.id]=a; |
3696224d BH |
241 | } |
242 | ||
243 | Resolver d_resolver; | |
3696224d BH |
244 | }; |
245 | ||
246 | void CommunicatorClass::slaveRefresh(PacketHandler *P) | |
247 | { | |
248 | UeberBackend *B=dynamic_cast<UeberBackend *>(P->getBackend()); | |
16e654fa BH |
249 | vector<DomainInfo> rdomains; |
250 | vector<pair<DomainInfo, bool> > sdomains; | |
dbcb3066 | 251 | B->getUnfreshSlaveInfos(&rdomains); |
16e654fa | 252 | DNSSECKeeper dk; |
dbcb3066 BH |
253 | { |
254 | Lock l(&d_lock); | |
255 | typedef UniQueue::index<IDTag>::type domains_by_name_t; | |
256 | domains_by_name_t& nameindex=boost::multi_index::get<IDTag>(d_suckdomains); | |
257 | ||
dbcb3066 BH |
258 | BOOST_FOREACH(DomainInfo& di, rdomains) { |
259 | SuckRequest sr; | |
260 | sr.domain=di.zone; | |
261 | if(di.masters.empty()) // slave domains w/o masters are ignored | |
262 | continue; | |
263 | // remove unfresh domains already queued for AXFR, no sense polling them again | |
264 | sr.master=*di.masters.begin(); | |
265 | if(nameindex.count(sr)) | |
266 | continue; | |
16e654fa BH |
267 | |
268 | sdomains.push_back(make_pair(di, dk.isPresigned(di.zone))); | |
dbcb3066 | 269 | } |
dbcb3066 BH |
270 | } |
271 | ||
3696224d BH |
272 | if(sdomains.empty()) |
273 | { | |
dbcb3066 BH |
274 | if(d_slaveschanged) { |
275 | Lock l(&d_lock); | |
276 | L<<Logger::Warning<<"No new unfresh slave domains, "<<d_suckdomains.size()<<" queued for AXFR already"<<endl; | |
277 | } | |
278 | d_slaveschanged = !rdomains.empty(); | |
3696224d BH |
279 | return; |
280 | } | |
dbcb3066 BH |
281 | else { |
282 | Lock l(&d_lock); | |
3696224d BH |
283 | L<<Logger::Warning<<sdomains.size()<<" slave domain"<<(sdomains.size()>1 ? "s" : "")<<" need"<< |
284 | (sdomains.size()>1 ? "" : "s")<< | |
dbcb3066 BH |
285 | " checking, "<<d_suckdomains.size()<<" queued for AXFR"<<endl; |
286 | } | |
3696224d BH |
287 | |
288 | SlaveSenderReceiver ssr; | |
16e654fa | 289 | Inflighter<vector<pair<DomainInfo, bool> >, SlaveSenderReceiver> ifl(sdomains, ssr); |
3696224d BH |
290 | |
291 | ifl.d_maxInFlight = 200; | |
292 | ||
293 | for(;;) { | |
294 | try { | |
295 | ifl.run(); | |
296 | break; | |
297 | } | |
dbcb3066 | 298 | catch(std::exception& e) { |
3696224d BH |
299 | L<<Logger::Error<<"While checking domain freshness: " << e.what()<<endl; |
300 | } | |
301 | catch(AhuException &re) { | |
302 | L<<Logger::Error<<"While checking domain freshness: " << re.reason<<endl; | |
303 | } | |
304 | } | |
0c01dd7c | 305 | L<<Logger::Warning<<"Received serial number updates for "<<ssr.d_freshness.size()<<" zones, had "<<ifl.getTimeouts()<<" timeouts"<<endl; |
16e654fa BH |
306 | |
307 | typedef pair<DomainInfo, bool> val_t; | |
308 | BOOST_FOREACH(val_t& val, sdomains) { | |
309 | DomainInfo& di(val.first); | |
54b2edb4 | 310 | if(!ssr.d_freshness.count(di.id)) |
3696224d | 311 | continue; |
54b2edb4 | 312 | uint32_t theirserial = ssr.d_freshness[di.id].theirSerial, ourserial = di.serial; |
3696224d BH |
313 | |
314 | if(theirserial < ourserial) { | |
315 | L<<Logger::Error<<"Domain "<<di.zone<<" more recent than master, our serial " << ourserial << " > their serial "<< theirserial << endl; | |
316 | di.backend->setFresh(di.id); | |
317 | } | |
318 | else if(theirserial == ourserial) { | |
54b2edb4 BH |
319 | if(!dk.isPresigned(di.zone)) { |
320 | L<<Logger::Warning<<"Domain "<< di.zone<<" is fresh (not presigned, no RRSIG check)"<<endl; | |
321 | di.backend->setFresh(di.id); | |
322 | } | |
323 | else { | |
324 | B->lookup(QType(QType::RRSIG), di.zone); | |
325 | DNSResourceRecord rr; | |
326 | uint32_t maxExpire=0, maxInception=0; | |
327 | while(B->get(rr)) { | |
328 | RRSIGRecordContent rrc(rr.content); | |
329 | if(rrc.d_type == QType::SOA) { | |
330 | maxInception = std::max(maxInception, rrc.d_siginception); | |
331 | maxExpire = std::max(maxExpire, rrc.d_sigexpire); | |
332 | } | |
333 | } | |
334 | if(maxInception == ssr.d_freshness[di.id].theirInception && maxExpire == ssr.d_freshness[di.id].theirExpire) { | |
335 | L<<Logger::Warning<<"Domain "<< di.zone<<" is fresh and apex RRSIGs match"<<endl; | |
336 | di.backend->setFresh(di.id); | |
337 | } | |
338 | else { | |
339 | L<<Logger::Warning<<"Domain "<< di.zone<<" is fresh, but RRSIGS differ, so DNSSEC stale"<<endl; | |
340 | addSuckRequest(di.zone, *di.masters.begin()); | |
341 | } | |
342 | } | |
3696224d BH |
343 | } |
344 | else { | |
345 | L<<Logger::Warning<<"Domain "<< di.zone<<" is stale, master serial "<<theirserial<<", our serial "<< ourserial <<endl; | |
346 | addSuckRequest(di.zone, *di.masters.begin()); | |
3696224d BH |
347 | } |
348 | } | |
3696224d BH |
349 | } |
350 |