2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
25 #include "auth-caches.hh"
28 #include "communicator.hh"
30 #include <boost/utility.hpp>
32 #include "dnsbackend.hh"
33 #include "ueberbackend.hh"
34 #include "packethandler.hh"
35 #include "nameserver.hh"
36 #include "resolver.hh"
39 #include "arguments.hh"
40 #include "packetcache.hh"
42 #include "namespaces.hh"
45 void CommunicatorClass::queueNotifyDomain(const DomainInfo
& di
, UeberBackend
* B
)
47 bool hasQueuedItem
=false;
48 set
<string
> nsset
, ips
;
54 if (d_onlyNotify
.size()) {
55 B
->lookup(QType(QType::NS
), di
.zone
, di
.id
);
57 nsset
.insert(getRR
<NSRecordContent
>(rr
.dr
)->getNS().toString());
59 for(set
<string
>::const_iterator j
=nsset
.begin();j
!=nsset
.end();++j
) {
60 vector
<string
> nsips
=fns
.lookup(DNSName(*j
), B
);
62 g_log
<<Logger::Warning
<<"Unable to queue notification of domain '"<<di
.zone
<<"': nameservers do not resolve!"<<endl
;
64 for(vector
<string
>::const_iterator k
=nsips
.begin();k
!=nsips
.end();++k
) {
65 const ComboAddress
caIp(*k
, 53);
66 if(!d_preventSelfNotification
|| !AddressIsUs(caIp
)) {
67 if(!d_onlyNotify
.match(&caIp
))
68 g_log
<<Logger::Info
<<"Skipped notification of domain '"<<di
.zone
<<"' to "<<*j
<<" because it does not match only-notify."<<endl
;
70 ips
.insert(caIp
.toStringWithPort());
75 for(set
<string
>::const_iterator j
=ips
.begin();j
!=ips
.end();++j
) {
76 g_log
<<Logger::Warning
<<"Queued notification of domain '"<<di
.zone
<<"' to "<<*j
<<endl
;
82 catch (PDNSException
&ae
) {
83 g_log
<< Logger::Error
<< "Error looking up name servers for " << di
.zone
<< ", cannot notify: " << ae
.reason
<< endl
;
86 catch (std::exception
&e
) {
87 g_log
<< Logger::Error
<< "Error looking up name servers for " << di
.zone
<< ", cannot notify: " << e
.what() << endl
;
92 set
<string
> alsoNotify(d_alsoNotify
);
93 B
->alsoNotifies(di
.zone
, &alsoNotify
);
95 for(set
<string
>::const_iterator j
=alsoNotify
.begin();j
!=alsoNotify
.end();++j
) {
97 const ComboAddress
caIp(*j
, 53);
98 g_log
<<Logger::Warning
<<"Queued also-notification of domain '"<<di
.zone
<<"' to "<<caIp
.toStringWithPort()<<endl
;
99 if (!ips
.count(caIp
.toStringWithPort())) {
100 ips
.insert(caIp
.toStringWithPort());
101 d_nq
.add(di
.zone
, caIp
.toStringWithPort());
105 catch(PDNSException
&e
) {
106 g_log
<<Logger::Warning
<<"Unparseable IP in ALSO-NOTIFY metadata of domain '"<<di
.zone
<<"'. Warning: "<<e
.reason
<<endl
;
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
;
115 bool CommunicatorClass::notifyDomain(const DNSName
&domain
, UeberBackend
* B
)
118 if(!B
->getDomainInfo(domain
, di
)) {
119 g_log
<<Logger::Error
<<"No such domain '"<<domain
<<"' in our database"<<endl
;
122 queueNotifyDomain(di
, B
);
123 // call backend and tell them we sent out the notification - even though that is premature
124 if (di
.serial
!= di
.notified_serial
)
125 di
.backend
->setNotified(di
.id
, di
.serial
);
130 void NotificationQueue::dump()
132 cerr
<<"Waiting for notification responses: "<<endl
;
133 for(NotificationRequest
& nr
: d_nqueue
) {
134 cerr
<<nr
.domain
<<", "<<nr
.ip
<<endl
;
138 void CommunicatorClass::masterUpdateCheck(PacketHandler
*P
)
140 if(!::arg().mustDo("master"))
143 UeberBackend
*B
=P
->getBackend();
144 vector
<DomainInfo
> cmdomains
;
145 B
->getUpdatedMasters(&cmdomains
);
147 if(cmdomains
.empty()) {
149 g_log
<<Logger::Warning
<<"No master domains need notifications"<<endl
;
150 d_masterschanged
=false;
153 d_masterschanged
=true;
154 g_log
<<Logger::Error
<<cmdomains
.size()<<" domain"<<(cmdomains
.size()>1 ? "s" : "")<<" for which we are master need"<<
155 (cmdomains
.size()>1 ? "" : "s")<<
156 " notifications"<<endl
;
159 // figure out A records of everybody needing notification
160 // do this via the FindNS class, d_fns
162 for(auto& di
: cmdomains
) {
163 purgeAuthCachesExact(di
.zone
);
164 queueNotifyDomain(di
, B
);
165 di
.backend
->setNotified(di
.id
, di
.serial
);
169 time_t CommunicatorClass::doNotifications(PacketHandler
*P
)
171 UeberBackend
*B
=P
->getBackend();
173 Utility::socklen_t fromlen
;
177 set
<int> fds
= {d_nsock4
, d_nsock6
};
179 // receive incoming notifications on the nonblocking socket and take them off the list
180 while(waitForMultiData(fds
, 0, 0, &sock
) > 0) {
181 fromlen
=sizeof(from
);
182 size
=recvfrom(sock
,buffer
,sizeof(buffer
),0,(struct sockaddr
*)&from
,&fromlen
);
189 if(p
.parse(buffer
,(size_t)size
)<0) {
190 g_log
<<Logger::Warning
<<"Unable to parse SOA notification answer from "<<p
.getRemote()<<endl
;
195 g_log
<<Logger::Warning
<<"Received unsuccessful notification report for '"<<p
.qdomain
<<"' from "<<from
.toStringWithPort()<<", error: "<<RCode::to_s(p
.d
.rcode
)<<endl
;
197 if(d_nq
.removeIf(from
.toStringWithPort(), p
.d
.id
, p
.qdomain
))
198 g_log
<<Logger::Warning
<<"Removed from notification list: '"<<p
.qdomain
<<"' to "<<from
.toStringWithPort()<<" "<< (p
.d
.rcode
? RCode::to_s(p
.d
.rcode
) : "(was acknowledged)")<<endl
;
200 g_log
<<Logger::Warning
<<"Received spurious notify answer for '"<<p
.qdomain
<<"' from "<< from
.toStringWithPort()<<endl
;
205 // send out possible new notifications
211 while(d_nq
.getOne(domain
, ip
, &id
, purged
)) {
214 ComboAddress
remote(ip
, 53); // default to 53
215 if((d_nsock6
< 0 && remote
.sin4
.sin_family
== AF_INET6
) ||
216 (d_nsock4
< 0 && remote
.sin4
.sin_family
== AF_INET
)) {
217 g_log
<<Logger::Warning
<<"Unable to notify "<<remote
.toStringWithPort()<<" for domain '"<<domain
<<"', address family is disabled. Is query-local-address"<<(remote
.sin4
.sin_family
== AF_INET
? "" : "6")<<" unset?"<<endl
;
218 d_nq
.removeIf(remote
.toStringWithPort(), id
, domain
); // Remove, we'll never be able to notify
219 continue; // don't try to notify what we can't!
221 if(d_preventSelfNotification
&& AddressIsUs(remote
))
224 sendNotification(remote
.sin4
.sin_family
== AF_INET
? d_nsock4
: d_nsock6
, domain
, remote
, id
, B
);
225 drillHole(domain
, ip
);
227 catch(ResolverException
&re
) {
228 g_log
<<Logger::Error
<<"Error trying to resolve '"<<ip
<<"' for notifying '"<<domain
<<"' to server: "<<re
.reason
<<endl
;
232 g_log
<<Logger::Error
<<"Notification for "<<domain
<<" to "<<ip
<<" failed after retries"<<endl
;
235 return d_nq
.earliest();
238 void CommunicatorClass::sendNotification(int sock
, const DNSName
& domain
, const ComboAddress
& remote
, uint16_t id
, UeberBackend
*B
)
242 DNSName tsigalgorithm
;
246 if (::arg().mustDo("send-signed-notify") && B
->getDomainMetadata(domain
, "TSIG-ALLOW-AXFR", meta
) && meta
.size() > 0) {
247 tsigkeyname
= DNSName(meta
[0]);
250 vector
<uint8_t> packet
;
251 DNSPacketWriter
pw(packet
, domain
, QType::SOA
, 1, Opcode::Notify
);
252 pw
.getHeader()->id
= id
;
253 pw
.getHeader()->aa
= true;
255 if (tsigkeyname
.empty() == false) {
256 if (!B
->getTSIGKey(tsigkeyname
, &tsigalgorithm
, &tsigsecret64
)) {
257 g_log
<<Logger::Error
<<"TSIG key '"<<tsigkeyname
<<"' for domain '"<<domain
<<"' not found"<<endl
;
260 TSIGRecordContent trc
;
261 if (tsigalgorithm
.toStringNoDot() == "hmac-md5")
262 trc
.d_algoName
= DNSName(tsigalgorithm
.toStringNoDot() + ".sig-alg.reg.int.");
264 trc
.d_algoName
= tsigalgorithm
;
265 trc
.d_time
= time(0);
267 trc
.d_origID
=ntohs(id
);
269 if (B64Decode(tsigsecret64
, tsigsecret
) == -1) {
270 g_log
<<Logger::Error
<<"Unable to Base-64 decode TSIG key '"<<tsigkeyname
<<"' for domain '"<<domain
<<"'"<<endl
;
273 addTSIG(pw
, trc
, tsigkeyname
, tsigsecret
, "", false);
276 if(sendto(sock
, &packet
[0], packet
.size(), 0, (struct sockaddr
*)(&remote
), remote
.getSocklen()) < 0) {
277 throw ResolverException("Unable to send notify to "+remote
.toStringWithPort()+": "+stringerror());
281 void CommunicatorClass::drillHole(const DNSName
&domain
, const string
&ip
)
284 d_holes
[make_pair(domain
,ip
)]=time(0);
287 bool CommunicatorClass::justNotified(const DNSName
&domain
, const string
&ip
)
290 if(d_holes
.find(make_pair(domain
,ip
))==d_holes
.end()) // no hole
293 if(d_holes
[make_pair(domain
,ip
)]>time(0)-900) // recent hole
296 // do we want to purge this? XXX FIXME
300 void CommunicatorClass::makeNotifySockets()
302 if(!::arg()["query-local-address"].empty()) {
303 d_nsock4
= makeQuerySocket(ComboAddress(::arg()["query-local-address"]), true, ::arg().mustDo("non-local-bind"));
307 if(!::arg()["query-local-address6"].empty()) {
308 d_nsock6
= makeQuerySocket(ComboAddress(::arg()["query-local-address6"]), true, ::arg().mustDo("non-local-bind"));
314 void CommunicatorClass::notify(const DNSName
&domain
, const string
&ip
)
316 d_nq
.add(domain
, ip
);