]>
Commit | Line | Data |
---|---|---|
3696224d | 1 | /* |
12471842 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 | |
bf269e28 | 25 | #include "auth-caches.hh" |
9931f46f | 26 | #include "auth-zonecache.hh" |
3696224d | 27 | #include "utility.hh" |
dc6aa7f5 | 28 | #include <cerrno> |
3696224d BH |
29 | #include "communicator.hh" |
30 | #include <set> | |
31 | #include <boost/utility.hpp> | |
fa8fd4d2 | 32 | |
3696224d BH |
33 | #include "dnsbackend.hh" |
34 | #include "ueberbackend.hh" | |
35 | #include "packethandler.hh" | |
8db49a64 | 36 | #include "nameserver.hh" |
3696224d BH |
37 | #include "resolver.hh" |
38 | #include "logger.hh" | |
39 | #include "dns.hh" | |
40 | #include "arguments.hh" | |
3696224d | 41 | #include "packetcache.hh" |
7014a23d | 42 | #include "base64.hh" |
3696224d | 43 | #include "namespaces.hh" |
20829585 | 44 | #include "query-local-address.hh" |
3696224d | 45 | |
3696224d | 46 | |
abcd36a1 | 47 | void CommunicatorClass::queueNotifyDomain(const DomainInfo& di, UeberBackend* B) |
3696224d | 48 | { |
088c3342 | 49 | bool hasQueuedItem=false; |
1fd89ec2 PD |
50 | set<string> ips; |
51 | set<DNSName> nsset; | |
66157226 | 52 | DNSZoneRecord rr; |
eac85b18 KM |
53 | FindNS fns; |
54 | ||
410f8bd3 | 55 | try { |
99844905 | 56 | if (d_onlyNotify.size()) { |
4bd5eb80 | 57 | B->lookup(QType(QType::NS), di.zone, di.id); |
99844905 | 58 | while(B->get(rr)) |
1fd89ec2 | 59 | nsset.insert(getRR<NSRecordContent>(rr.dr)->getNS()); |
eac85b18 | 60 | |
1fd89ec2 PD |
61 | for(const auto & ns : nsset) { |
62 | vector<string> nsips=fns.lookup(ns, B); | |
99844905 | 63 | if(nsips.empty()) |
1fd89ec2 | 64 | g_log<<Logger::Warning<<"Unable to queue notification of domain '"<<di.zone<<"' to nameserver '"<<ns<<"': nameserver does not resolve!"<<endl; |
99844905 | 65 | else |
d7f67000 RP |
66 | for(const auto & nsip : nsips) { |
67 | const ComboAddress caIp(nsip, 53); | |
99844905 KD |
68 | if(!d_preventSelfNotification || !AddressIsUs(caIp)) { |
69 | if(!d_onlyNotify.match(&caIp)) | |
1fd89ec2 | 70 | g_log<<Logger::Notice<<"Skipped notification of domain '"<<di.zone<<"' to "<<ns<<" because "<<caIp<<" does not match only-notify."<<endl; |
99844905 KD |
71 | else |
72 | ips.insert(caIp.toStringWithPort()); | |
73 | } | |
68bb0e57 | 74 | } |
99844905 | 75 | } |
eac85b18 | 76 | |
d7f67000 RP |
77 | for(const auto & ip : ips) { |
78 | g_log<<Logger::Notice<<"Queued notification of domain '"<<di.zone<<"' to "<<ip<<endl; | |
79 | d_nq.add(di.zone,ip); | |
99844905 KD |
80 | hasQueuedItem=true; |
81 | } | |
3696224d | 82 | } |
410f8bd3 PD |
83 | } |
84 | catch (PDNSException &ae) { | |
85 | g_log << Logger::Error << "Error looking up name servers for " << di.zone << ", cannot notify: " << ae.reason << endl; | |
86 | return; | |
87 | } | |
88 | catch (std::exception &e) { | |
89 | g_log << Logger::Error << "Error looking up name servers for " << di.zone << ", cannot notify: " << e.what() << endl; | |
90 | return; | |
91 | } | |
92 | ||
eac85b18 | 93 | |
24d3239e | 94 | set<string> alsoNotify(d_alsoNotify); |
abcd36a1 | 95 | B->alsoNotifies(di.zone, &alsoNotify); |
eac85b18 | 96 | |
d7f67000 | 97 | for(const auto & j : alsoNotify) { |
79a454ef | 98 | try { |
d7f67000 | 99 | const ComboAddress caIp(j, 53); |
c2923b81 | 100 | g_log<<Logger::Notice<<"Queued also-notification of domain '"<<di.zone<<"' to "<<caIp.toStringWithPort()<<endl; |
79a454ef KM |
101 | if (!ips.count(caIp.toStringWithPort())) { |
102 | ips.insert(caIp.toStringWithPort()); | |
abcd36a1 | 103 | d_nq.add(di.zone, caIp.toStringWithPort()); |
79a454ef KM |
104 | } |
105 | hasQueuedItem=true; | |
106 | } | |
107 | catch(PDNSException &e) { | |
e6a9dde5 | 108 | g_log<<Logger::Warning<<"Unparseable IP in ALSO-NOTIFY metadata of domain '"<<di.zone<<"'. Warning: "<<e.reason<<endl; |
24d3239e | 109 | } |
3696224d | 110 | } |
eac85b18 | 111 | |
088c3342 | 112 | if (!hasQueuedItem) |
e6a9dde5 | 113 | g_log<<Logger::Warning<<"Request to queue notification for domain '"<<di.zone<<"' was processed, but no valid nameservers or ALSO-NOTIFYs found. Not notifying!"<<endl; |
3696224d BH |
114 | } |
115 | ||
eac85b18 | 116 | |
3497eb18 | 117 | bool CommunicatorClass::notifyDomain(const DNSName &domain, UeberBackend* B) |
3696224d BH |
118 | { |
119 | DomainInfo di; | |
3497eb18 | 120 | if(!B->getDomainInfo(domain, di)) { |
c2923b81 | 121 | g_log<<Logger::Warning<<"No such domain '"<<domain<<"' in our database"<<endl; |
3696224d BH |
122 | return false; |
123 | } | |
3497eb18 | 124 | queueNotifyDomain(di, B); |
f159e137 KM |
125 | // call backend and tell them we sent out the notification - even though that is premature |
126 | if (di.serial != di.notified_serial) | |
127 | di.backend->setNotified(di.id, di.serial); | |
3696224d | 128 | |
de42d2df | 129 | return true; |
3696224d BH |
130 | } |
131 | ||
2d00c43d BH |
132 | void NotificationQueue::dump() |
133 | { | |
134 | cerr<<"Waiting for notification responses: "<<endl; | |
ef7cd021 | 135 | for(NotificationRequest& nr : d_nqueue) { |
af729f37 | 136 | cerr<<nr.domain<<", "<<nr.ip<<endl; |
2d00c43d BH |
137 | } |
138 | } | |
3696224d | 139 | |
ddeea7a6 KM |
140 | void CommunicatorClass::getUpdatedProducers(UeberBackend* B, vector<DomainInfo>& domains, const std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) |
141 | { | |
142 | std::string metaHash; | |
143 | std::string mapHash; | |
144 | for (auto& ch : catalogHashes) { | |
ddeea7a6 KM |
145 | if (!catalogs.count(ch.first)) { |
146 | g_log << Logger::Warning << "orphaned member zones found with catalog '" << ch.first << "'" << endl; | |
147 | continue; | |
148 | } | |
149 | ||
150 | if (!B->getDomainMetadata(ch.first, "CATALOG-HASH", metaHash)) { | |
151 | metaHash.clear(); | |
152 | } | |
153 | ||
154 | mapHash = Base64Encode(ch.second.digest()); | |
155 | if (mapHash != metaHash) { | |
156 | DomainInfo di; | |
157 | if (B->getDomainInfo(ch.first, di)) { | |
158 | if (di.kind != DomainInfo::Producer) { | |
159 | g_log << Logger::Warning << "zone '" << di.zone << "' is no producer zone" << endl; | |
160 | continue; | |
161 | } | |
162 | ||
163 | B->setDomainMetadata(di.zone, "CATALOG-HASH", mapHash); | |
164 | ||
98d26417 | 165 | g_log << Logger::Warning << "new CATALOG-HASH '" << mapHash << "' for zone '" << di.zone << "'" << endl; |
ddeea7a6 KM |
166 | |
167 | SOAData sd; | |
168 | if (!B->getSOAUncached(di.zone, sd)) { | |
169 | g_log << Logger::Warning << "SOA lookup failed for producer zone '" << di.zone << "'" << endl; | |
170 | continue; | |
171 | } | |
172 | ||
173 | DNSResourceRecord rr; | |
174 | makeIncreasedSOARecord(sd, "EPOCH", "", rr); | |
175 | di.backend->startTransaction(sd.qname, -1); | |
176 | if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) { | |
177 | di.backend->abortTransaction(); | |
178 | throw PDNSException("backend hosting producer zone '" + sd.qname.toLogString() + "' does not support editing records"); | |
179 | } | |
180 | di.backend->commitTransaction(); | |
181 | ||
182 | domains.emplace_back(di); | |
183 | } | |
184 | } | |
185 | } | |
186 | } | |
187 | ||
3696224d BH |
188 | void CommunicatorClass::masterUpdateCheck(PacketHandler *P) |
189 | { | |
2c70d169 | 190 | if(!::arg().mustDo("primary")) |
ddeea7a6 | 191 | return; |
3696224d | 192 | |
3971cf53 | 193 | UeberBackend *B=P->getBackend(); |
3696224d | 194 | vector<DomainInfo> cmdomains; |
ddeea7a6 KM |
195 | std::unordered_set<DNSName> catalogs; |
196 | CatalogHashMap catalogHashes; | |
197 | B->getUpdatedMasters(cmdomains, catalogs, catalogHashes); | |
198 | getUpdatedProducers(B, cmdomains, catalogs, catalogHashes); | |
199 | ||
3696224d | 200 | if(cmdomains.empty()) { |
ddeea7a6 | 201 | g_log << Logger::Info << "no primary or producer domains need notifications" << endl; |
3696224d BH |
202 | } |
203 | else { | |
ddeea7a6 | 204 | g_log << Logger::Info << cmdomains.size() << " domain" << addS(cmdomains.size()) << " for which we are primary or consumer need" << addS(cmdomains.size()) << " notifications" << endl; |
3696224d BH |
205 | } |
206 | ||
abcd36a1 | 207 | for(auto& di : cmdomains) { |
bf269e28 | 208 | purgeAuthCachesExact(di.zone); |
9931f46f | 209 | g_zoneCache.add(di.zone, di.id); |
abcd36a1 CH |
210 | queueNotifyDomain(di, B); |
211 | di.backend->setNotified(di.id, di.serial); | |
3696224d BH |
212 | } |
213 | } | |
214 | ||
de42d2df | 215 | time_t CommunicatorClass::doNotifications(PacketHandler* P) |
3696224d | 216 | { |
de42d2df | 217 | UeberBackend* B = P->getBackend(); |
3696224d | 218 | ComboAddress from; |
3696224d | 219 | char buffer[1500]; |
a683e8bd | 220 | int sock; |
d529011f | 221 | set<int> fds = {d_nsock4, d_nsock6}; |
b9ec63fa | 222 | |
a71bee29 | 223 | // receive incoming notifications on the nonblocking socket and take them off the list |
de42d2df | 224 | while (waitForMultiData(fds, 0, 0, &sock) > 0) { |
bd6223db FM |
225 | Utility::socklen_t fromlen = sizeof(from); |
226 | const auto size = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&from, &fromlen); | |
227 | if (size < 0) { | |
0c01dd7c | 228 | break; |
bd6223db | 229 | } |
27c0050c | 230 | DNSPacket p(true); |
3696224d BH |
231 | |
232 | p.setRemote(&from); | |
233 | ||
de42d2df FM |
234 | if (p.parse(buffer, (size_t)size) < 0) { |
235 | g_log << Logger::Warning << "Unable to parse SOA notification answer from " << p.getRemote() << endl; | |
3696224d BH |
236 | continue; |
237 | } | |
238 | ||
bd6223db | 239 | if (p.d.rcode) { |
de42d2df | 240 | g_log << Logger::Warning << "Received unsuccessful notification report for '" << p.qdomain << "' from " << from.toStringWithPort() << ", error: " << RCode::to_s(p.d.rcode) << endl; |
bd6223db | 241 | } |
33966bfb | 242 | |
792548c4 | 243 | if (d_nq.removeIf(from, p.d.id, p.qdomain)) { |
de42d2df | 244 | g_log << Logger::Notice << "Removed from notification list: '" << p.qdomain << "' to " << from.toStringWithPort() << " " << (p.d.rcode ? RCode::to_s(p.d.rcode) : "(was acknowledged)") << endl; |
bd6223db | 245 | } |
2d00c43d | 246 | else { |
de42d2df FM |
247 | g_log << Logger::Warning << "Received spurious notify answer for '" << p.qdomain << "' from " << from.toStringWithPort() << endl; |
248 | // d_nq.dump(); | |
2d00c43d | 249 | } |
3696224d BH |
250 | } |
251 | ||
252 | // send out possible new notifications | |
675fa24c PD |
253 | DNSName domain; |
254 | string ip; | |
de42d2df | 255 | uint16_t id = 0; |
3696224d BH |
256 | |
257 | bool purged; | |
de42d2df FM |
258 | while (d_nq.getOne(domain, ip, &id, purged)) { |
259 | if (!purged) { | |
3696224d | 260 | try { |
0c01dd7c | 261 | ComboAddress remote(ip, 53); // default to 53 |
de42d2df FM |
262 | if ((d_nsock6 < 0 && remote.sin4.sin_family == AF_INET6) || (d_nsock4 < 0 && remote.sin4.sin_family == AF_INET)) { |
263 | g_log << Logger::Warning << "Unable to notify " << remote.toStringWithPort() << " for domain '" << domain << "', address family is disabled. Is an IPv" << (remote.sin4.sin_family == AF_INET ? "4" : "6") << " address set in query-local-address?" << endl; | |
792548c4 | 264 | d_nq.removeIf(remote, id, domain); // Remove, we'll never be able to notify |
de42d2df | 265 | continue; // don't try to notify what we can't! |
9d36589a | 266 | } |
bd6223db | 267 | if (d_preventSelfNotification && AddressIsUs(remote)) { |
232f0877 | 268 | continue; |
bd6223db | 269 | } |
8db49a64 | 270 | |
3497eb18 | 271 | sendNotification(remote.sin4.sin_family == AF_INET ? d_nsock4 : d_nsock6, domain, remote, id, B); |
4957a608 | 272 | drillHole(domain, ip); |
3696224d | 273 | } |
de42d2df FM |
274 | catch (ResolverException& re) { |
275 | g_log << Logger::Warning << "Error trying to resolve '" << ip << "' for notifying '" << domain << "' to server: " << re.reason << endl; | |
3696224d BH |
276 | } |
277 | } | |
bd6223db | 278 | else { |
de42d2df | 279 | g_log << Logger::Warning << "Notification for " << domain << " to " << ip << " failed after retries" << endl; |
bd6223db | 280 | } |
3696224d BH |
281 | } |
282 | ||
283 | return d_nq.earliest(); | |
284 | } | |
285 | ||
3497eb18 | 286 | void CommunicatorClass::sendNotification(int sock, const DNSName& domain, const ComboAddress& remote, uint16_t id, UeberBackend *B) |
a71bee29 | 287 | { |
7014a23d | 288 | vector<string> meta; |
6fe866b4 AT |
289 | DNSName tsigkeyname; |
290 | DNSName tsigalgorithm; | |
7014a23d AT |
291 | string tsigsecret64; |
292 | string tsigsecret; | |
293 | ||
3497eb18 | 294 | if (::arg().mustDo("send-signed-notify") && B->getDomainMetadata(domain, "TSIG-ALLOW-AXFR", meta) && meta.size() > 0) { |
9d423514 | 295 | tsigkeyname = DNSName(meta[0]); |
7014a23d AT |
296 | } |
297 | ||
a71bee29 PD |
298 | vector<uint8_t> packet; |
299 | DNSPacketWriter pw(packet, domain, QType::SOA, 1, Opcode::Notify); | |
300 | pw.getHeader()->id = id; | |
de42d2df | 301 | pw.getHeader()->aa = true; |
a71bee29 | 302 | |
7014a23d | 303 | if (tsigkeyname.empty() == false) { |
40361bf2 KM |
304 | if (!B->getTSIGKey(tsigkeyname, tsigalgorithm, tsigsecret64)) { |
305 | g_log << Logger::Error << "TSIG key '" << tsigkeyname << "' for domain '" << domain << "' not found" << endl; | |
fba65bff PL |
306 | return; |
307 | } | |
7014a23d | 308 | TSIGRecordContent trc; |
9d423514 AT |
309 | if (tsigalgorithm.toStringNoDot() == "hmac-md5") |
310 | trc.d_algoName = DNSName(tsigalgorithm.toStringNoDot() + ".sig-alg.reg.int."); | |
7014a23d AT |
311 | else |
312 | trc.d_algoName = tsigalgorithm; | |
4646277d | 313 | trc.d_time = time(nullptr); |
7014a23d AT |
314 | trc.d_fudge = 300; |
315 | trc.d_origID=ntohs(id); | |
316 | trc.d_eRcode=0; | |
fba65bff | 317 | if (B64Decode(tsigsecret64, tsigsecret) == -1) { |
e6a9dde5 | 318 | g_log<<Logger::Error<<"Unable to Base-64 decode TSIG key '"<<tsigkeyname<<"' for domain '"<<domain<<"'"<<endl; |
fba65bff PL |
319 | return; |
320 | } | |
ea3816cf | 321 | addTSIG(pw, trc, tsigkeyname, tsigsecret, "", false); |
7014a23d AT |
322 | } |
323 | ||
a71bee29 PD |
324 | if(sendto(sock, &packet[0], packet.size(), 0, (struct sockaddr*)(&remote), remote.getSocklen()) < 0) { |
325 | throw ResolverException("Unable to send notify to "+remote.toStringWithPort()+": "+stringerror()); | |
326 | } | |
327 | } | |
328 | ||
5fca2e23 | 329 | void CommunicatorClass::drillHole(const DNSName &domain, const string &ip) |
3696224d | 330 | { |
faa05786 | 331 | (*d_holes.lock())[pair(domain,ip)]=time(nullptr); |
3696224d BH |
332 | } |
333 | ||
5fca2e23 | 334 | bool CommunicatorClass::justNotified(const DNSName &domain, const string &ip) |
3696224d | 335 | { |
01e96795 | 336 | auto holes = d_holes.lock(); |
faa05786 | 337 | auto it = holes->find(pair(domain,ip)); |
01e96795 RG |
338 | if (it == holes->end()) { |
339 | // no hole | |
3696224d | 340 | return false; |
01e96795 | 341 | } |
3696224d | 342 | |
01e96795 RG |
343 | if (it->second > time(nullptr)-900) { |
344 | // recent hole | |
3696224d | 345 | return true; |
01e96795 | 346 | } |
3696224d | 347 | |
de42d2df | 348 | // do we want to purge this? XXX FIXME |
3696224d BH |
349 | return false; |
350 | } | |
351 | ||
0c01dd7c | 352 | void CommunicatorClass::makeNotifySockets() |
3696224d | 353 | { |
20829585 PL |
354 | if(pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) { |
355 | d_nsock4 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET, 0), true, ::arg().mustDo("non-local-bind")); | |
9d36589a PL |
356 | } else { |
357 | d_nsock4 = -1; | |
358 | } | |
20829585 PL |
359 | if(pdns::isQueryLocalAddressFamilyEnabled(AF_INET6)) { |
360 | d_nsock6 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET6, 0), true, ::arg().mustDo("non-local-bind")); | |
9d36589a | 361 | } else { |
0c01dd7c | 362 | d_nsock6 = -1; |
9d36589a | 363 | } |
3696224d BH |
364 | } |
365 | ||
5fca2e23 | 366 | void CommunicatorClass::notify(const DNSName &domain, const string &ip) |
3696224d BH |
367 | { |
368 | d_nq.add(domain, ip); | |
3696224d BH |
369 | d_any_sem.post(); |
370 | } |