| 1 | /* |
|---|
| 2 | PowerDNS Versatile Database Driven Nameserver |
|---|
| 3 | Copyright (C) 2002-2012 PowerDNS.COM BV |
|---|
| 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" |
|---|
| 20 | #include "dnssecinfra.hh" |
|---|
| 21 | #include "dnsseckeeper.hh" |
|---|
| 22 | #include "base32.hh" |
|---|
| 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> |
|---|
| 38 | #include "base64.hh" |
|---|
| 39 | #include "inflighter.cc" |
|---|
| 40 | #include "lua-auth.hh" |
|---|
| 41 | #include "namespaces.hh" |
|---|
| 42 | #include "common_startup.hh" |
|---|
| 43 | #include <boost/scoped_ptr.hpp> |
|---|
| 44 | using boost::scoped_ptr; |
|---|
| 45 | |
|---|
| 46 | template<typename T> bool rfc1982LessThan(T a, T b) |
|---|
| 47 | { |
|---|
| 48 | return ((signed)(a - b)) < 0; |
|---|
| 49 | } |
|---|
| 50 | |
|---|
| 51 | void CommunicatorClass::addSuckRequest(const string &domain, const string &master) |
|---|
| 52 | { |
|---|
| 53 | Lock l(&d_lock); |
|---|
| 54 | SuckRequest sr; |
|---|
| 55 | sr.domain = domain; |
|---|
| 56 | sr.master = master; |
|---|
| 57 | pair<UniQueue::iterator, bool> res; |
|---|
| 58 | |
|---|
| 59 | res=d_suckdomains.push_back(sr); |
|---|
| 60 | |
|---|
| 61 | if(res.second) { |
|---|
| 62 | d_suck_sem.post(); |
|---|
| 63 | } |
|---|
| 64 | } |
|---|
| 65 | |
|---|
| 66 | void CommunicatorClass::suck(const string &domain,const string &remote) |
|---|
| 67 | { |
|---|
| 68 | L<<Logger::Error<<"Initiating transfer of '"<<domain<<"' from remote '"<<remote<<"'"<<endl; |
|---|
| 69 | uint32_t domain_id; |
|---|
| 70 | PacketHandler P; // fresh UeberBackend |
|---|
| 71 | |
|---|
| 72 | DomainInfo di; |
|---|
| 73 | di.backend=0; |
|---|
| 74 | bool first=true; |
|---|
| 75 | try { |
|---|
| 76 | UeberBackend *B=dynamic_cast<UeberBackend *>(P.getBackend()); // copy of the same UeberBackend |
|---|
| 77 | NSEC3PARAMRecordContent ns3pr, hadNs3pr; |
|---|
| 78 | bool narrow, hadNarrow=false; |
|---|
| 79 | DNSSECKeeper dk; // has its own ueberbackend |
|---|
| 80 | bool dnssecZone = false; |
|---|
| 81 | bool haveNSEC3=false; |
|---|
| 82 | if(dk.isSecuredZone(domain)) { |
|---|
| 83 | dnssecZone=true; |
|---|
| 84 | haveNSEC3=dk.getNSEC3PARAM(domain, &ns3pr, &narrow); |
|---|
| 85 | if (haveNSEC3) { |
|---|
| 86 | hadNs3pr = ns3pr; |
|---|
| 87 | hadNarrow = narrow; |
|---|
| 88 | } |
|---|
| 89 | } |
|---|
| 90 | |
|---|
| 91 | const bool hadNSEC3 = haveNSEC3; |
|---|
| 92 | const bool hadPresigned = dk.isPresigned(domain); |
|---|
| 93 | const bool hadDnssecZone = dnssecZone; |
|---|
| 94 | |
|---|
| 95 | if(dnssecZone) { |
|---|
| 96 | if(!haveNSEC3) |
|---|
| 97 | L<<Logger::Info<<"Adding NSEC ordering information"<<endl; |
|---|
| 98 | else if(!narrow) |
|---|
| 99 | L<<Logger::Info<<"Adding NSEC3 hashed ordering information for '"<<domain<<"'"<<endl; |
|---|
| 100 | else |
|---|
| 101 | L<<Logger::Info<<"Erasing NSEC3 ordering since we are narrow, only setting 'auth' fields"<<endl; |
|---|
| 102 | } |
|---|
| 103 | |
|---|
| 104 | if(!B->getDomainInfo(domain, di) || !di.backend) { // di.backend and B are mostly identical |
|---|
| 105 | L<<Logger::Error<<"Can't determine backend for domain '"<<domain<<"'"<<endl; |
|---|
| 106 | return; |
|---|
| 107 | } |
|---|
| 108 | domain_id=di.id; |
|---|
| 109 | |
|---|
| 110 | Resolver::res_t recs; |
|---|
| 111 | set<string> nsset, qnames, dsnames, nonterm, delnonterm; |
|---|
| 112 | |
|---|
| 113 | ComboAddress raddr(remote, 53); |
|---|
| 114 | |
|---|
| 115 | string tsigkeyname, tsigalgorithm, tsigsecret; |
|---|
| 116 | |
|---|
| 117 | if(dk.getTSIGForAccess(domain, remote, &tsigkeyname)) { |
|---|
| 118 | string tsigsecret64; |
|---|
| 119 | if(B->getTSIGKey(tsigkeyname, &tsigalgorithm, &tsigsecret64)) |
|---|
| 120 | { |
|---|
| 121 | B64Decode(tsigsecret64, tsigsecret); |
|---|
| 122 | } |
|---|
| 123 | else |
|---|
| 124 | { |
|---|
| 125 | L<<Logger::Error<<"TSIG key '"<<tsigkeyname<<"' not found, ignoring 'AXFR-MASTER-TSIG' for domain '"<<domain<<"'"<<endl; |
|---|
| 126 | tsigkeyname=""; |
|---|
| 127 | } |
|---|
| 128 | } |
|---|
| 129 | |
|---|
| 130 | scoped_ptr<AuthLua> pdl; |
|---|
| 131 | vector<string> scripts; |
|---|
| 132 | if(B->getDomainMetadata(domain, "LUA-AXFR-SCRIPT", scripts) && !scripts.empty()) { |
|---|
| 133 | try { |
|---|
| 134 | pdl.reset(new AuthLua(scripts[0])); |
|---|
| 135 | L<<Logger::Info<<"Loaded Lua script '"<<scripts[0]<<"' to edit the incoming AXFR of '"<<domain<<"'"<<endl; |
|---|
| 136 | } |
|---|
| 137 | catch(std::exception& e) { |
|---|
| 138 | L<<Logger::Error<<"Failed to load Lua editing script '"<<scripts[0]<<"' for incoming AXFR of '"<<domain<<"': "<<e.what()<<endl; |
|---|
| 139 | return; |
|---|
| 140 | } |
|---|
| 141 | } |
|---|
| 142 | |
|---|
| 143 | vector<string> localaddr; |
|---|
| 144 | ComboAddress laddr; |
|---|
| 145 | |
|---|
| 146 | if(B->getDomainMetadata(domain, "AXFR-SOURCE", localaddr) && !localaddr.empty()) { |
|---|
| 147 | try { |
|---|
| 148 | laddr = ComboAddress(localaddr[0]); |
|---|
| 149 | L<<Logger::Info<<"AXFR source for domain '"<<domain<<"' set to "<<localaddr[0]<<endl; |
|---|
| 150 | } |
|---|
| 151 | catch(std::exception& e) { |
|---|
| 152 | L<<Logger::Error<<"Failed to load AXFR source '"<<localaddr[0]<<"' for incoming AXFR of '"<<domain<<"': "<<e.what()<<endl; |
|---|
| 153 | return; |
|---|
| 154 | } |
|---|
| 155 | } else { |
|---|
| 156 | laddr.sin4.sin_family = 0; |
|---|
| 157 | } |
|---|
| 158 | |
|---|
| 159 | AXFRRetriever retriever(raddr, domain.c_str(), tsigkeyname, tsigalgorithm, tsigsecret, |
|---|
| 160 | (laddr.sin4.sin_family == 0) ? NULL : &laddr); |
|---|
| 161 | |
|---|
| 162 | bool gotPresigned = false; |
|---|
| 163 | bool gotNSEC3 = false; |
|---|
| 164 | bool gotOptOutFlag = false; |
|---|
| 165 | unsigned int soa_serial = 0; |
|---|
| 166 | while(retriever.getChunk(recs)) { |
|---|
| 167 | if(first) { |
|---|
| 168 | L<<Logger::Error<<"AXFR started for '"<<domain<<"', transaction started"<<endl; |
|---|
| 169 | di.backend->startTransaction(domain, domain_id); |
|---|
| 170 | first=false; |
|---|
| 171 | } |
|---|
| 172 | |
|---|
| 173 | for(Resolver::res_t::iterator i=recs.begin();i!=recs.end();++i) { |
|---|
| 174 | if(i->qtype.getCode() == QType::OPT || i->qtype.getCode() == QType::TSIG) // ignore EDNS0 & TSIG |
|---|
| 175 | continue; |
|---|
| 176 | |
|---|
| 177 | if(i->qtype.getCode() == QType::SOA) { |
|---|
| 178 | if(soa_serial != 0) |
|---|
| 179 | continue; //skip the last SOA |
|---|
| 180 | SOAData sd; |
|---|
| 181 | fillSOAData(i->content,sd); |
|---|
| 182 | soa_serial = sd.serial; |
|---|
| 183 | } |
|---|
| 184 | |
|---|
| 185 | // we generate NSEC, NSEC3, NSEC3PARAM (sorry Olafur) on the fly, this could only confuse things |
|---|
| 186 | if (i->qtype.getCode() == QType::NSEC3PARAM) { |
|---|
| 187 | ns3pr = NSEC3PARAMRecordContent(i->content); |
|---|
| 188 | narrow = false; |
|---|
| 189 | dnssecZone = haveNSEC3 = gotPresigned = gotNSEC3 = true; |
|---|
| 190 | continue; |
|---|
| 191 | } else if (i->qtype.getCode() == QType::NSEC3) { |
|---|
| 192 | dnssecZone = gotPresigned = true; |
|---|
| 193 | gotOptOutFlag = NSEC3RecordContent(i->content).d_flags & 1; |
|---|
| 194 | continue; |
|---|
| 195 | } else if (i->qtype.getCode() == QType::NSEC) { |
|---|
| 196 | dnssecZone = gotPresigned = true; |
|---|
| 197 | continue; |
|---|
| 198 | } |
|---|
| 199 | |
|---|
| 200 | if(!endsOn(i->qname, domain)) { |
|---|
| 201 | 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; |
|---|
| 202 | continue; |
|---|
| 203 | } |
|---|
| 204 | |
|---|
| 205 | i->domain_id=domain_id; |
|---|
| 206 | if (i->qtype.getCode() == QType::SRV) |
|---|
| 207 | i->content = stripDot(i->content); |
|---|
| 208 | #if 0 |
|---|
| 209 | if(i->qtype.getCode()>=60000) |
|---|
| 210 | throw DBException("Database can't store unknown record type "+lexical_cast<string>(i->qtype.getCode()-1024)); |
|---|
| 211 | #endif |
|---|
| 212 | vector<DNSResourceRecord> out; |
|---|
| 213 | if(pdl && pdl->axfrfilter(raddr, domain, *i, out)) { |
|---|
| 214 | BOOST_FOREACH(const DNSResourceRecord& rr, out) { |
|---|
| 215 | di.backend->feedRecord(rr); |
|---|
| 216 | if(rr.qtype.getCode() == QType::NS && !pdns_iequals(rr.qname, domain)) |
|---|
| 217 | nsset.insert(rr.qname); |
|---|
| 218 | if(rr.qtype.getCode() != QType::RRSIG) // this excludes us hashing RRSIGs for NSEC(3) |
|---|
| 219 | qnames.insert(rr.qname); |
|---|
| 220 | if(i->qtype.getCode() == QType::DS) |
|---|
| 221 | dsnames.insert(i->qname); |
|---|
| 222 | } |
|---|
| 223 | } |
|---|
| 224 | else { |
|---|
| 225 | di.backend->feedRecord(*i); |
|---|
| 226 | if(i->qtype.getCode() == QType::NS && !pdns_iequals(i->qname, domain)) |
|---|
| 227 | nsset.insert(i->qname); |
|---|
| 228 | if(i->qtype.getCode() != QType::RRSIG) // this excludes us hashing RRSIGs for NSEC(3) |
|---|
| 229 | qnames.insert(i->qname); |
|---|
| 230 | if(i->qtype.getCode() == QType::DS) |
|---|
| 231 | dsnames.insert(i->qname); |
|---|
| 232 | } |
|---|
| 233 | } |
|---|
| 234 | } |
|---|
| 235 | |
|---|
| 236 | if (hadPresigned && !gotNSEC3) |
|---|
| 237 | { |
|---|
| 238 | // we only had NSEC3 because we were a presigned zone... |
|---|
| 239 | haveNSEC3 = false; |
|---|
| 240 | } |
|---|
| 241 | |
|---|
| 242 | bool doent=true; |
|---|
| 243 | bool realrr=true; |
|---|
| 244 | string hashed; |
|---|
| 245 | |
|---|
| 246 | uint32_t maxent = ::arg().asNum("max-ent-entries"); |
|---|
| 247 | |
|---|
| 248 | dononterm:; |
|---|
| 249 | BOOST_FOREACH(const string& qname, qnames) |
|---|
| 250 | { |
|---|
| 251 | bool auth=true; |
|---|
| 252 | string shorter(qname); |
|---|
| 253 | |
|---|
| 254 | if(realrr) { |
|---|
| 255 | do { |
|---|
| 256 | if(nsset.count(shorter)) { |
|---|
| 257 | auth=false; |
|---|
| 258 | break; |
|---|
| 259 | } |
|---|
| 260 | }while(chopOff(shorter)); |
|---|
| 261 | } |
|---|
| 262 | |
|---|
| 263 | if(dnssecZone && haveNSEC3) |
|---|
| 264 | { |
|---|
| 265 | if(!narrow) { |
|---|
| 266 | hashed=toLower(toBase32Hex(hashQNameWithSalt(ns3pr.d_iterations, ns3pr.d_salt, qname))); |
|---|
| 267 | di.backend->updateDNSSECOrderAndAuthAbsolute(domain_id, qname, hashed, auth); |
|---|
| 268 | } |
|---|
| 269 | else |
|---|
| 270 | di.backend->nullifyDNSSECOrderNameAndUpdateAuth(domain_id, qname, auth); |
|---|
| 271 | if(realrr) |
|---|
| 272 | { |
|---|
| 273 | if (dsnames.count(qname)) |
|---|
| 274 | di.backend->setDNSSECAuthOnDsRecord(domain_id, qname); |
|---|
| 275 | if (!auth || nsset.count(qname)) { |
|---|
| 276 | di.backend->nullifyDNSSECOrderNameAndAuth(domain_id, qname, "NS"); |
|---|
| 277 | di.backend->nullifyDNSSECOrderNameAndAuth(domain_id, qname, "A"); |
|---|
| 278 | di.backend->nullifyDNSSECOrderNameAndAuth(domain_id, qname, "AAAA"); |
|---|
| 279 | } |
|---|
| 280 | } |
|---|
| 281 | } |
|---|
| 282 | else // NSEC |
|---|
| 283 | { |
|---|
| 284 | if(realrr) |
|---|
| 285 | { |
|---|
| 286 | di.backend->updateDNSSECOrderAndAuth(domain_id, domain, qname, auth); |
|---|
| 287 | if (dsnames.count(qname)) |
|---|
| 288 | di.backend->setDNSSECAuthOnDsRecord(domain_id, qname); |
|---|
| 289 | if (!auth || nsset.count(qname)) { |
|---|
| 290 | di.backend->nullifyDNSSECOrderNameAndAuth(domain_id, qname, "A"); |
|---|
| 291 | di.backend->nullifyDNSSECOrderNameAndAuth(domain_id, qname, "AAAA"); |
|---|
| 292 | } |
|---|
| 293 | } |
|---|
| 294 | } |
|---|
| 295 | |
|---|
| 296 | if(auth && realrr && doent) |
|---|
| 297 | { |
|---|
| 298 | shorter=qname; |
|---|
| 299 | while(!pdns_iequals(shorter, domain) && chopOff(shorter)) |
|---|
| 300 | { |
|---|
| 301 | if(!qnames.count(shorter) && !nonterm.count(shorter)) |
|---|
| 302 | { |
|---|
| 303 | if(!(maxent)) |
|---|
| 304 | { |
|---|
| 305 | L<<Logger::Error<<"AXFR zone "<<domain<<" has too many empty non terminals."<<endl; |
|---|
| 306 | nonterm.empty(); |
|---|
| 307 | doent=false; |
|---|
| 308 | break; |
|---|
| 309 | } |
|---|
| 310 | nonterm.insert(shorter); |
|---|
| 311 | --maxent; |
|---|
| 312 | } |
|---|
| 313 | } |
|---|
| 314 | } |
|---|
| 315 | } |
|---|
| 316 | |
|---|
| 317 | if(!nonterm.empty() && realrr && doent) |
|---|
| 318 | { |
|---|
| 319 | if(di.backend->updateEmptyNonTerminals(domain_id, domain, nonterm, delnonterm, false)) |
|---|
| 320 | { |
|---|
| 321 | realrr=false; |
|---|
| 322 | qnames=nonterm; |
|---|
| 323 | goto dononterm; |
|---|
| 324 | } |
|---|
| 325 | } |
|---|
| 326 | |
|---|
| 327 | // now we also need to update the presigned flag and NSEC3PARAM |
|---|
| 328 | // for the zone |
|---|
| 329 | if (gotPresigned) { |
|---|
| 330 | if (!hadDnssecZone && !hadPresigned) { |
|---|
| 331 | // zone is now presigned |
|---|
| 332 | dk.setPresigned(domain); |
|---|
| 333 | } |
|---|
| 334 | |
|---|
| 335 | if (hadPresigned || !hadDnssecZone) |
|---|
| 336 | { |
|---|
| 337 | // this is a presigned zone, update NSEC3PARAM |
|---|
| 338 | if (gotNSEC3) { |
|---|
| 339 | ns3pr.d_flags = gotOptOutFlag ? 1 : 0; |
|---|
| 340 | // only update if there was a change |
|---|
| 341 | if (!hadNSEC3 || (narrow != hadNarrow) || |
|---|
| 342 | (ns3pr.d_algorithm != hadNs3pr.d_algorithm) || |
|---|
| 343 | (ns3pr.d_flags != hadNs3pr.d_flags) || |
|---|
| 344 | (ns3pr.d_iterations != hadNs3pr.d_iterations) || |
|---|
| 345 | (ns3pr.d_salt != hadNs3pr.d_salt)) { |
|---|
| 346 | dk.setNSEC3PARAM(domain, ns3pr, narrow); |
|---|
| 347 | } |
|---|
| 348 | } else if (hadNSEC3) { |
|---|
| 349 | dk.unsetNSEC3PARAM(domain); |
|---|
| 350 | } |
|---|
| 351 | } |
|---|
| 352 | } else if (hadPresigned) { |
|---|
| 353 | // zone is no longer presigned |
|---|
| 354 | dk.unsetPresigned(domain); |
|---|
| 355 | dk.unsetNSEC3PARAM(domain); |
|---|
| 356 | } |
|---|
| 357 | |
|---|
| 358 | di.backend->commitTransaction(); |
|---|
| 359 | di.backend->setFresh(domain_id); |
|---|
| 360 | PC.purge(domain+"$"); |
|---|
| 361 | |
|---|
| 362 | |
|---|
| 363 | L<<Logger::Error<<"AXFR done for '"<<domain<<"', zone committed with serial number "<<soa_serial<<endl; |
|---|
| 364 | if(::arg().mustDo("slave-renotify")) |
|---|
| 365 | notifyDomain(domain); |
|---|
| 366 | } |
|---|
| 367 | catch(DBException &re) { |
|---|
| 368 | L<<Logger::Error<<"Unable to feed record during incoming AXFR of '"+domain+"': "<<re.reason<<endl; |
|---|
| 369 | if(di.backend && !first) { |
|---|
| 370 | L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; |
|---|
| 371 | di.backend->abortTransaction(); |
|---|
| 372 | } |
|---|
| 373 | } |
|---|
| 374 | catch(MOADNSException &re) { |
|---|
| 375 | L<<Logger::Error<<"Unable to parse record during incoming AXFR of '"+domain+"' (MOADNSException): "<<re.what()<<endl; |
|---|
| 376 | if(di.backend && !first) { |
|---|
| 377 | L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; |
|---|
| 378 | di.backend->abortTransaction(); |
|---|
| 379 | } |
|---|
| 380 | } |
|---|
| 381 | catch(std::exception &re) { |
|---|
| 382 | L<<Logger::Error<<"Unable to parse record during incoming AXFR of '"+domain+"' (std::exception): "<<re.what()<<endl; |
|---|
| 383 | if(di.backend && !first) { |
|---|
| 384 | L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; |
|---|
| 385 | di.backend->abortTransaction(); |
|---|
| 386 | } |
|---|
| 387 | } |
|---|
| 388 | catch(ResolverException &re) { |
|---|
| 389 | L<<Logger::Error<<"Unable to AXFR zone '"+domain+"' from remote '"<<remote<<"' (resolver): "<<re.reason<<endl; |
|---|
| 390 | if(di.backend && !first) { |
|---|
| 391 | L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; |
|---|
| 392 | di.backend->abortTransaction(); |
|---|
| 393 | } |
|---|
| 394 | } |
|---|
| 395 | catch(AhuException &ae) { |
|---|
| 396 | L<<Logger::Error<<"Unable to AXFR zone '"+domain+"' from remote '"<<remote<<"' (AhuException): "<<ae.reason<<endl; |
|---|
| 397 | if(di.backend && !first) { |
|---|
| 398 | L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; |
|---|
| 399 | di.backend->abortTransaction(); |
|---|
| 400 | } |
|---|
| 401 | } |
|---|
| 402 | } |
|---|
| 403 | namespace { |
|---|
| 404 | struct QueryInfo |
|---|
| 405 | { |
|---|
| 406 | struct timeval query_ttd; |
|---|
| 407 | uint16_t id; |
|---|
| 408 | }; |
|---|
| 409 | |
|---|
| 410 | struct DomainNotificationInfo |
|---|
| 411 | { |
|---|
| 412 | DomainInfo di; |
|---|
| 413 | bool dnssecOk; |
|---|
| 414 | string tsigkeyname, tsigalgname, tsigsecret; |
|---|
| 415 | }; |
|---|
| 416 | } |
|---|
| 417 | |
|---|
| 418 | |
|---|
| 419 | struct SlaveSenderReceiver |
|---|
| 420 | { |
|---|
| 421 | typedef pair<string, uint16_t> Identifier; |
|---|
| 422 | |
|---|
| 423 | struct Answer { |
|---|
| 424 | uint32_t theirSerial; |
|---|
| 425 | uint32_t theirInception; |
|---|
| 426 | uint32_t theirExpire; |
|---|
| 427 | }; |
|---|
| 428 | |
|---|
| 429 | map<uint32_t, Answer> d_freshness; |
|---|
| 430 | |
|---|
| 431 | SlaveSenderReceiver() |
|---|
| 432 | { |
|---|
| 433 | } |
|---|
| 434 | |
|---|
| 435 | void deliverTimeout(const Identifier& i) |
|---|
| 436 | { |
|---|
| 437 | } |
|---|
| 438 | |
|---|
| 439 | Identifier send(DomainNotificationInfo& dni) |
|---|
| 440 | { |
|---|
| 441 | random_shuffle(dni.di.masters.begin(), dni.di.masters.end()); |
|---|
| 442 | try { |
|---|
| 443 | ComboAddress remote(*dni.di.masters.begin()); |
|---|
| 444 | return make_pair(dni.di.zone, |
|---|
| 445 | d_resolver.sendResolve(ComboAddress(*dni.di.masters.begin(), 53), |
|---|
| 446 | dni.di.zone.c_str(), |
|---|
| 447 | QType::SOA, |
|---|
| 448 | dni.dnssecOk, dni.tsigkeyname, dni.tsigalgname, dni.tsigsecret) |
|---|
| 449 | ); |
|---|
| 450 | } |
|---|
| 451 | catch(AhuException& e) { |
|---|
| 452 | throw runtime_error("While attempting to query freshness of '"+dni.di.zone+"': "+e.reason); |
|---|
| 453 | } |
|---|
| 454 | } |
|---|
| 455 | |
|---|
| 456 | bool receive(Identifier& id, Answer& a) |
|---|
| 457 | { |
|---|
| 458 | if(d_resolver.tryGetSOASerial(&id.first, &a.theirSerial, &a.theirInception, &a.theirExpire, &id.second)) { |
|---|
| 459 | return 1; |
|---|
| 460 | } |
|---|
| 461 | return 0; |
|---|
| 462 | } |
|---|
| 463 | |
|---|
| 464 | void deliverAnswer(DomainNotificationInfo& dni, const Answer& a, unsigned int usec) |
|---|
| 465 | { |
|---|
| 466 | d_freshness[dni.di.id]=a; |
|---|
| 467 | } |
|---|
| 468 | |
|---|
| 469 | Resolver d_resolver; |
|---|
| 470 | }; |
|---|
| 471 | |
|---|
| 472 | void CommunicatorClass::addSlaveCheckRequest(const DomainInfo& di, const ComboAddress& remote) |
|---|
| 473 | { |
|---|
| 474 | Lock l(&d_lock); |
|---|
| 475 | DomainInfo ours = di; |
|---|
| 476 | ours.backend = 0; |
|---|
| 477 | d_tocheck.insert(ours); |
|---|
| 478 | d_any_sem.post(); // kick the loop! |
|---|
| 479 | } |
|---|
| 480 | |
|---|
| 481 | void CommunicatorClass::addTrySuperMasterRequest(DNSPacket *p) |
|---|
| 482 | { |
|---|
| 483 | Lock l(&d_lock); |
|---|
| 484 | DNSPacket ours = *p; |
|---|
| 485 | d_potentialsupermasters.push_back(ours); |
|---|
| 486 | d_any_sem.post(); // kick the loop! |
|---|
| 487 | } |
|---|
| 488 | |
|---|
| 489 | void CommunicatorClass::slaveRefresh(PacketHandler *P) |
|---|
| 490 | { |
|---|
| 491 | UeberBackend *B=dynamic_cast<UeberBackend *>(P->getBackend()); |
|---|
| 492 | vector<DomainInfo> rdomains; |
|---|
| 493 | vector<DomainNotificationInfo> sdomains; // the bool is for 'presigned' |
|---|
| 494 | vector<DNSPacket> trysuperdomains; |
|---|
| 495 | |
|---|
| 496 | { |
|---|
| 497 | Lock l(&d_lock); |
|---|
| 498 | rdomains.insert(rdomains.end(), d_tocheck.begin(), d_tocheck.end()); |
|---|
| 499 | d_tocheck.clear(); |
|---|
| 500 | trysuperdomains.insert(trysuperdomains.end(), d_potentialsupermasters.begin(), d_potentialsupermasters.end()); |
|---|
| 501 | d_potentialsupermasters.clear(); |
|---|
| 502 | } |
|---|
| 503 | |
|---|
| 504 | BOOST_FOREACH(DNSPacket& dp, trysuperdomains) { |
|---|
| 505 | int res; |
|---|
| 506 | res=P->trySuperMasterSynchronous(&dp); |
|---|
| 507 | if(res>=0) { |
|---|
| 508 | DNSPacket *r=dp.replyPacket(); |
|---|
| 509 | r->setRcode(res); |
|---|
| 510 | r->setOpcode(Opcode::Notify); |
|---|
| 511 | N->send(r); |
|---|
| 512 | delete r; |
|---|
| 513 | } |
|---|
| 514 | } |
|---|
| 515 | |
|---|
| 516 | if(rdomains.empty()) // if we have priority domains, check them first |
|---|
| 517 | B->getUnfreshSlaveInfos(&rdomains); |
|---|
| 518 | |
|---|
| 519 | DNSSECKeeper dk(B); // NOW HEAR THIS! This DK uses our B backend, so no interleaved access! |
|---|
| 520 | { |
|---|
| 521 | Lock l(&d_lock); |
|---|
| 522 | domains_by_name_t& nameindex=boost::multi_index::get<IDTag>(d_suckdomains); |
|---|
| 523 | |
|---|
| 524 | BOOST_FOREACH(DomainInfo& di, rdomains) { |
|---|
| 525 | SuckRequest sr; |
|---|
| 526 | sr.domain=di.zone; |
|---|
| 527 | if(di.masters.empty()) // slave domains w/o masters are ignored |
|---|
| 528 | continue; |
|---|
| 529 | // remove unfresh domains already queued for AXFR, no sense polling them again |
|---|
| 530 | sr.master=*di.masters.begin(); |
|---|
| 531 | if(nameindex.count(sr)) { |
|---|
| 532 | continue; |
|---|
| 533 | } |
|---|
| 534 | DomainNotificationInfo dni; |
|---|
| 535 | dni.di=di; |
|---|
| 536 | dni.dnssecOk = dk.isPresigned(di.zone); |
|---|
| 537 | |
|---|
| 538 | if(dk.getTSIGForAccess(di.zone, sr.master, &dni.tsigkeyname)) { |
|---|
| 539 | string secret64; |
|---|
| 540 | B->getTSIGKey(dni.tsigkeyname, &dni.tsigalgname, &secret64); |
|---|
| 541 | B64Decode(secret64, dni.tsigsecret); |
|---|
| 542 | } |
|---|
| 543 | sdomains.push_back(dni); |
|---|
| 544 | } |
|---|
| 545 | } |
|---|
| 546 | |
|---|
| 547 | if(sdomains.empty()) |
|---|
| 548 | { |
|---|
| 549 | if(d_slaveschanged) { |
|---|
| 550 | Lock l(&d_lock); |
|---|
| 551 | L<<Logger::Warning<<"No new unfresh slave domains, "<<d_suckdomains.size()<<" queued for AXFR already"<<endl; |
|---|
| 552 | } |
|---|
| 553 | d_slaveschanged = !rdomains.empty(); |
|---|
| 554 | return; |
|---|
| 555 | } |
|---|
| 556 | else { |
|---|
| 557 | Lock l(&d_lock); |
|---|
| 558 | L<<Logger::Warning<<sdomains.size()<<" slave domain"<<(sdomains.size()>1 ? "s" : "")<<" need"<< |
|---|
| 559 | (sdomains.size()>1 ? "" : "s")<< |
|---|
| 560 | " checking, "<<d_suckdomains.size()<<" queued for AXFR"<<endl; |
|---|
| 561 | } |
|---|
| 562 | |
|---|
| 563 | SlaveSenderReceiver ssr; |
|---|
| 564 | |
|---|
| 565 | Inflighter<vector<DomainNotificationInfo>, SlaveSenderReceiver> ifl(sdomains, ssr); |
|---|
| 566 | |
|---|
| 567 | ifl.d_maxInFlight = 200; |
|---|
| 568 | |
|---|
| 569 | for(;;) { |
|---|
| 570 | try { |
|---|
| 571 | ifl.run(); |
|---|
| 572 | break; |
|---|
| 573 | } |
|---|
| 574 | catch(std::exception& e) { |
|---|
| 575 | L<<Logger::Error<<"While checking domain freshness: " << e.what()<<endl; |
|---|
| 576 | } |
|---|
| 577 | catch(AhuException &re) { |
|---|
| 578 | L<<Logger::Error<<"While checking domain freshness: " << re.reason<<endl; |
|---|
| 579 | } |
|---|
| 580 | } |
|---|
| 581 | L<<Logger::Warning<<"Received serial number updates for "<<ssr.d_freshness.size()<<" zones, had "<<ifl.getTimeouts()<<" timeouts"<<endl; |
|---|
| 582 | |
|---|
| 583 | typedef DomainNotificationInfo val_t; |
|---|
| 584 | BOOST_FOREACH(val_t& val, sdomains) { |
|---|
| 585 | DomainInfo& di(val.di); |
|---|
| 586 | // might've come from the packethandler |
|---|
| 587 | if(!di.backend && !B->getDomainInfo(di.zone, di)) { |
|---|
| 588 | L<<Logger::Warning<<"Ignore domain "<< di.zone<<" since it has been removed from our backend"<<endl; |
|---|
| 589 | continue; |
|---|
| 590 | } |
|---|
| 591 | |
|---|
| 592 | if(!ssr.d_freshness.count(di.id)) |
|---|
| 593 | continue; |
|---|
| 594 | uint32_t theirserial = ssr.d_freshness[di.id].theirSerial, ourserial = di.serial; |
|---|
| 595 | |
|---|
| 596 | if(rfc1982LessThan(theirserial, ourserial)) { |
|---|
| 597 | L<<Logger::Error<<"Domain '"<<di.zone<<"' more recent than master, our serial " << ourserial << " > their serial "<< theirserial << endl; |
|---|
| 598 | di.backend->setFresh(di.id); |
|---|
| 599 | } |
|---|
| 600 | else if(theirserial == ourserial) { |
|---|
| 601 | if(!dk.isPresigned(di.zone)) { |
|---|
| 602 | L<<Logger::Warning<<"Domain '"<< di.zone<<"' is fresh (not presigned, no RRSIG check)"<<endl; |
|---|
| 603 | di.backend->setFresh(di.id); |
|---|
| 604 | } |
|---|
| 605 | else { |
|---|
| 606 | B->lookup(QType(QType::RRSIG), di.zone); // can't use DK before we are done with this lookup! |
|---|
| 607 | DNSResourceRecord rr; |
|---|
| 608 | uint32_t maxExpire=0, maxInception=0; |
|---|
| 609 | while(B->get(rr)) { |
|---|
| 610 | RRSIGRecordContent rrc(rr.content); |
|---|
| 611 | if(rrc.d_type == QType::SOA) { |
|---|
| 612 | maxInception = std::max(maxInception, rrc.d_siginception); |
|---|
| 613 | maxExpire = std::max(maxExpire, rrc.d_sigexpire); |
|---|
| 614 | } |
|---|
| 615 | } |
|---|
| 616 | if(maxInception == ssr.d_freshness[di.id].theirInception && maxExpire == ssr.d_freshness[di.id].theirExpire) { |
|---|
| 617 | L<<Logger::Warning<<"Domain '"<< di.zone<<"' is fresh and apex RRSIGs match"<<endl; |
|---|
| 618 | di.backend->setFresh(di.id); |
|---|
| 619 | } |
|---|
| 620 | else { |
|---|
| 621 | L<<Logger::Warning<<"Domain '"<< di.zone<<"' is fresh, but RRSIGS differ, so DNSSEC stale"<<endl; |
|---|
| 622 | addSuckRequest(di.zone, *di.masters.begin()); |
|---|
| 623 | } |
|---|
| 624 | } |
|---|
| 625 | } |
|---|
| 626 | else { |
|---|
| 627 | L<<Logger::Warning<<"Domain '"<< di.zone<<"' is stale, master serial "<<theirserial<<", our serial "<< ourserial <<endl; |
|---|
| 628 | addSuckRequest(di.zone, *di.masters.begin()); |
|---|
| 629 | } |
|---|
| 630 | } |
|---|
| 631 | } |
|---|
| 632 | |
|---|
| 633 | // stub for PowerDNSLua linking |
|---|
| 634 | int directResolve(const std::string& qname, const QType& qtype, int qclass, vector<DNSResourceRecord>& ret) |
|---|
| 635 | { |
|---|
| 636 | return -1; |
|---|
| 637 | } |
|---|
| 638 | |
|---|
| 639 | |
|---|