]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/mastercommunicator.cc
Merge pull request #5556 from zilopbg/luabackend-getsoa
[thirdparty/pdns.git] / pdns / mastercommunicator.cc
1 /*
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 */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include "auth-caches.hh"
26 #include "utility.hh"
27 #include <errno.h>
28 #include "communicator.hh"
29 #include <set>
30 #include <boost/utility.hpp>
31
32 #include "dnsbackend.hh"
33 #include "ueberbackend.hh"
34 #include "packethandler.hh"
35 #include "nameserver.hh"
36 #include "resolver.hh"
37 #include "logger.hh"
38 #include "dns.hh"
39 #include "arguments.hh"
40 #include "packetcache.hh"
41 #include "base64.hh"
42 #include "namespaces.hh"
43
44
45 void CommunicatorClass::queueNotifyDomain(const DomainInfo& di, UeberBackend* B)
46 {
47 bool hasQueuedItem=false;
48 set<string> nsset, ips;
49 DNSZoneRecord rr;
50 FindNS fns;
51
52
53 if (d_onlyNotify.size()) {
54 B->lookup(QType(QType::NS), di.zone);
55 while(B->get(rr))
56 nsset.insert(getRR<NSRecordContent>(rr.dr)->getNS().toString());
57
58 for(set<string>::const_iterator j=nsset.begin();j!=nsset.end();++j) {
59 vector<string> nsips=fns.lookup(DNSName(*j), B);
60 if(nsips.empty())
61 L<<Logger::Warning<<"Unable to queue notification of domain '"<<di.zone<<"': nameservers do not resolve!"<<endl;
62 else
63 for(vector<string>::const_iterator k=nsips.begin();k!=nsips.end();++k) {
64 const ComboAddress caIp(*k, 53);
65 if(!d_preventSelfNotification || !AddressIsUs(caIp)) {
66 if(!d_onlyNotify.match(&caIp))
67 L<<Logger::Info<<"Skipped notification of domain '"<<di.zone<<"' to "<<*j<<" because it does not match only-notify."<<endl;
68 else
69 ips.insert(caIp.toStringWithPort());
70 }
71 }
72 }
73
74 for(set<string>::const_iterator j=ips.begin();j!=ips.end();++j) {
75 L<<Logger::Warning<<"Queued notification of domain '"<<di.zone<<"' to "<<*j<<endl;
76 d_nq.add(di.zone,*j);
77 hasQueuedItem=true;
78 }
79 }
80
81 set<string> alsoNotify(d_alsoNotify);
82 B->alsoNotifies(di.zone, &alsoNotify);
83
84 for(set<string>::const_iterator j=alsoNotify.begin();j!=alsoNotify.end();++j) {
85 try {
86 const ComboAddress caIp(*j, 53);
87 L<<Logger::Warning<<"Queued also-notification of domain '"<<di.zone<<"' to "<<caIp.toStringWithPort()<<endl;
88 if (!ips.count(caIp.toStringWithPort())) {
89 ips.insert(caIp.toStringWithPort());
90 d_nq.add(di.zone, caIp.toStringWithPort());
91 }
92 hasQueuedItem=true;
93 }
94 catch(PDNSException &e) {
95 L<<Logger::Warning<<"Unparseable IP in ALSO-NOTIFY metadata of domain '"<<di.zone<<"'. Warning: "<<e.reason<<endl;
96 }
97 }
98
99 if (!hasQueuedItem)
100 L<<Logger::Warning<<"Request to queue notification for domain '"<<di.zone<<"' was processed, but no valid nameservers or ALSO-NOTIFYs found. Not notifying!"<<endl;
101 }
102
103
104 bool CommunicatorClass::notifyDomain(const DNSName &domain)
105 {
106 DomainInfo di;
107 UeberBackend B;
108 if(!B.getDomainInfo(domain, di)) {
109 L<<Logger::Error<<"No such domain '"<<domain<<"' in our database"<<endl;
110 return false;
111 }
112 queueNotifyDomain(di, &B);
113 // call backend and tell them we sent out the notification - even though that is premature
114 di.backend->setNotified(di.id, di.serial);
115
116 return true;
117 }
118
119 void NotificationQueue::dump()
120 {
121 cerr<<"Waiting for notification responses: "<<endl;
122 for(NotificationRequest& nr : d_nqueue) {
123 cerr<<nr.domain<<", "<<nr.ip<<endl;
124 }
125 }
126
127 void CommunicatorClass::masterUpdateCheck(PacketHandler *P)
128 {
129 if(!::arg().mustDo("master"))
130 return;
131
132 UeberBackend *B=P->getBackend();
133 vector<DomainInfo> cmdomains;
134 B->getUpdatedMasters(&cmdomains);
135
136 if(cmdomains.empty()) {
137 if(d_masterschanged)
138 L<<Logger::Warning<<"No master domains need notifications"<<endl;
139 d_masterschanged=false;
140 }
141 else {
142 d_masterschanged=true;
143 L<<Logger::Error<<cmdomains.size()<<" domain"<<(cmdomains.size()>1 ? "s" : "")<<" for which we are master need"<<
144 (cmdomains.size()>1 ? "" : "s")<<
145 " notifications"<<endl;
146 }
147
148 // figure out A records of everybody needing notification
149 // do this via the FindNS class, d_fns
150
151 for(auto& di : cmdomains) {
152 purgeAuthCachesExact(di.zone);
153 queueNotifyDomain(di, B);
154 di.backend->setNotified(di.id, di.serial);
155 }
156 }
157
158 time_t CommunicatorClass::doNotifications()
159 {
160 ComboAddress from;
161 Utility::socklen_t fromlen;
162 char buffer[1500];
163 int sock;
164 ssize_t size;
165
166 // receive incoming notifications on the nonblocking socket and take them off the list
167 while(waitFor2Data(d_nsock4, d_nsock6, 0, 0, &sock) > 0) {
168 fromlen=sizeof(from);
169 size=recvfrom(sock,buffer,sizeof(buffer),0,(struct sockaddr *)&from,&fromlen);
170 if(size < 0)
171 break;
172 DNSPacket p(true);
173
174 p.setRemote(&from);
175
176 if(p.parse(buffer,(size_t)size)<0) {
177 L<<Logger::Warning<<"Unable to parse SOA notification answer from "<<p.getRemote()<<endl;
178 continue;
179 }
180
181 if(p.d.rcode)
182 L<<Logger::Warning<<"Received unsuccessful notification report for '"<<p.qdomain<<"' from "<<from.toStringWithPort()<<", error: "<<RCode::to_s(p.d.rcode)<<endl;
183
184 if(d_nq.removeIf(from.toStringWithPort(), p.d.id, p.qdomain))
185 L<<Logger::Warning<<"Removed from notification list: '"<<p.qdomain<<"' to "<<from.toStringWithPort()<<" "<< (p.d.rcode ? RCode::to_s(p.d.rcode) : "(was acknowledged)")<<endl;
186 else {
187 L<<Logger::Warning<<"Received spurious notify answer for '"<<p.qdomain<<"' from "<< from.toStringWithPort()<<endl;
188 //d_nq.dump();
189 }
190 }
191
192 // send out possible new notifications
193 DNSName domain;
194 string ip;
195 uint16_t id;
196
197 bool purged;
198 while(d_nq.getOne(domain, ip, &id, purged)) {
199 if(!purged) {
200 try {
201 ComboAddress remote(ip, 53); // default to 53
202 if((d_nsock6 < 0 && remote.sin4.sin_family == AF_INET6) ||
203 (d_nsock4 < 0 && remote.sin4.sin_family == AF_INET))
204 continue; // don't try to notify what we can't!
205 if(d_preventSelfNotification && AddressIsUs(remote))
206 continue;
207
208 sendNotification(remote.sin4.sin_family == AF_INET ? d_nsock4 : d_nsock6, domain, remote, id);
209 drillHole(domain, ip);
210 }
211 catch(ResolverException &re) {
212 L<<Logger::Error<<"Error trying to resolve '"<<ip<<"' for notifying '"<<domain<<"' to server: "<<re.reason<<endl;
213 }
214 }
215 else
216 L<<Logger::Error<<"Notification for "<<domain<<" to "<<ip<<" failed after retries"<<endl;
217 }
218
219 return d_nq.earliest();
220 }
221
222 void CommunicatorClass::sendNotification(int sock, const DNSName& domain, const ComboAddress& remote, uint16_t id)
223 {
224 UeberBackend B;
225 vector<string> meta;
226 DNSName tsigkeyname;
227 DNSName tsigalgorithm;
228 string tsigsecret64;
229 string tsigsecret;
230
231 if (B.getDomainMetadata(domain, "TSIG-ALLOW-AXFR", meta) && meta.size() > 0) {
232 tsigkeyname = DNSName(meta[0]);
233 }
234
235 vector<uint8_t> packet;
236 DNSPacketWriter pw(packet, domain, QType::SOA, 1, Opcode::Notify);
237 pw.getHeader()->id = id;
238 pw.getHeader()->aa = true;
239
240 if (tsigkeyname.empty() == false) {
241 B.getTSIGKey(tsigkeyname, &tsigalgorithm, &tsigsecret64);
242 TSIGRecordContent trc;
243 if (tsigalgorithm.toStringNoDot() == "hmac-md5")
244 trc.d_algoName = DNSName(tsigalgorithm.toStringNoDot() + ".sig-alg.reg.int.");
245 else
246 trc.d_algoName = tsigalgorithm;
247 trc.d_time = time(0);
248 trc.d_fudge = 300;
249 trc.d_origID=ntohs(id);
250 trc.d_eRcode=0;
251 B64Decode(tsigsecret64, tsigsecret);
252 addTSIG(pw, trc, tsigkeyname, tsigsecret, "", false);
253 }
254
255 if(sendto(sock, &packet[0], packet.size(), 0, (struct sockaddr*)(&remote), remote.getSocklen()) < 0) {
256 throw ResolverException("Unable to send notify to "+remote.toStringWithPort()+": "+stringerror());
257 }
258 }
259
260 void CommunicatorClass::drillHole(const DNSName &domain, const string &ip)
261 {
262 Lock l(&d_holelock);
263 d_holes[make_pair(domain,ip)]=time(0);
264 }
265
266 bool CommunicatorClass::justNotified(const DNSName &domain, const string &ip)
267 {
268 Lock l(&d_holelock);
269 if(d_holes.find(make_pair(domain,ip))==d_holes.end()) // no hole
270 return false;
271
272 if(d_holes[make_pair(domain,ip)]>time(0)-900) // recent hole
273 return true;
274
275 // do we want to purge this? XXX FIXME
276 return false;
277 }
278
279 void CommunicatorClass::makeNotifySockets()
280 {
281 d_nsock4 = makeQuerySocket(ComboAddress(::arg()["query-local-address"]), true, ::arg().mustDo("non-local-bind"));
282 if(!::arg()["query-local-address6"].empty())
283 d_nsock6 = makeQuerySocket(ComboAddress(::arg()["query-local-address6"]), true, ::arg().mustDo("non-local-bind"));
284 else
285 d_nsock6 = -1;
286 }
287
288 void CommunicatorClass::notify(const DNSName &domain, const string &ip)
289 {
290 d_nq.add(domain, ip);
291 d_any_sem.post();
292 }