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