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
;
53 if (d_onlyNotify
.size()) {
54 B
->lookup(QType(QType::NS
), di
.zone
);
56 nsset
.insert(getRR
<NSRecordContent
>(rr
.dr
)->getNS().toString());
58 for(set
<string
>::const_iterator j
=nsset
.begin();j
!=nsset
.end();++j
) {
59 vector
<string
> nsips
=fns
.lookup(DNSName(*j
), B
);
61 L
<<Logger::Warning
<<"Unable to queue notification of domain '"<<di
.zone
<<"': nameservers do not resolve!"<<endl
;
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
;
69 ips
.insert(caIp
.toStringWithPort());
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
;
81 set
<string
> alsoNotify(d_alsoNotify
);
82 B
->alsoNotifies(di
.zone
, &alsoNotify
);
84 for(set
<string
>::const_iterator j
=alsoNotify
.begin();j
!=alsoNotify
.end();++j
) {
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());
94 catch(PDNSException
&e
) {
95 L
<<Logger::Warning
<<"Unparseable IP in ALSO-NOTIFY metadata of domain '"<<di
.zone
<<"'. Warning: "<<e
.reason
<<endl
;
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
;
104 bool CommunicatorClass::notifyDomain(const DNSName
&domain
)
108 if(!B
.getDomainInfo(domain
, di
)) {
109 L
<<Logger::Error
<<"No such domain '"<<domain
<<"' in our database"<<endl
;
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
);
119 void NotificationQueue::dump()
121 cerr
<<"Waiting for notification responses: "<<endl
;
122 for(NotificationRequest
& nr
: d_nqueue
) {
123 cerr
<<nr
.domain
<<", "<<nr
.ip
<<endl
;
127 void CommunicatorClass::masterUpdateCheck(PacketHandler
*P
)
129 if(!::arg().mustDo("master"))
132 UeberBackend
*B
=P
->getBackend();
133 vector
<DomainInfo
> cmdomains
;
134 B
->getUpdatedMasters(&cmdomains
);
136 if(cmdomains
.empty()) {
138 L
<<Logger::Warning
<<"No master domains need notifications"<<endl
;
139 d_masterschanged
=false;
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
;
148 // figure out A records of everybody needing notification
149 // do this via the FindNS class, d_fns
151 for(auto& di
: cmdomains
) {
152 purgeAuthCachesExact(di
.zone
);
153 queueNotifyDomain(di
, B
);
154 di
.backend
->setNotified(di
.id
, di
.serial
);
158 time_t CommunicatorClass::doNotifications()
161 Utility::socklen_t fromlen
;
165 set
<int> fds
= {d_nsock4
, d_nsock6
};
167 // receive incoming notifications on the nonblocking socket and take them off the list
168 while(waitForMultiData(fds
, 0, 0, &sock
) > 0) {
169 fromlen
=sizeof(from
);
170 size
=recvfrom(sock
,buffer
,sizeof(buffer
),0,(struct sockaddr
*)&from
,&fromlen
);
177 if(p
.parse(buffer
,(size_t)size
)<0) {
178 L
<<Logger::Warning
<<"Unable to parse SOA notification answer from "<<p
.getRemote()<<endl
;
183 L
<<Logger::Warning
<<"Received unsuccessful notification report for '"<<p
.qdomain
<<"' from "<<from
.toStringWithPort()<<", error: "<<RCode::to_s(p
.d
.rcode
)<<endl
;
185 if(d_nq
.removeIf(from
.toStringWithPort(), p
.d
.id
, p
.qdomain
))
186 L
<<Logger::Warning
<<"Removed from notification list: '"<<p
.qdomain
<<"' to "<<from
.toStringWithPort()<<" "<< (p
.d
.rcode
? RCode::to_s(p
.d
.rcode
) : "(was acknowledged)")<<endl
;
188 L
<<Logger::Warning
<<"Received spurious notify answer for '"<<p
.qdomain
<<"' from "<< from
.toStringWithPort()<<endl
;
193 // send out possible new notifications
199 while(d_nq
.getOne(domain
, ip
, &id
, purged
)) {
202 ComboAddress
remote(ip
, 53); // default to 53
203 if((d_nsock6
< 0 && remote
.sin4
.sin_family
== AF_INET6
) ||
204 (d_nsock4
< 0 && remote
.sin4
.sin_family
== AF_INET
)) {
205 L
<<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
;
206 d_nq
.removeIf(remote
.toStringWithPort(), id
, domain
); // Remove, we'll never be able to notify
207 continue; // don't try to notify what we can't!
209 if(d_preventSelfNotification
&& AddressIsUs(remote
))
212 sendNotification(remote
.sin4
.sin_family
== AF_INET
? d_nsock4
: d_nsock6
, domain
, remote
, id
);
213 drillHole(domain
, ip
);
215 catch(ResolverException
&re
) {
216 L
<<Logger::Error
<<"Error trying to resolve '"<<ip
<<"' for notifying '"<<domain
<<"' to server: "<<re
.reason
<<endl
;
220 L
<<Logger::Error
<<"Notification for "<<domain
<<" to "<<ip
<<" failed after retries"<<endl
;
223 return d_nq
.earliest();
226 void CommunicatorClass::sendNotification(int sock
, const DNSName
& domain
, const ComboAddress
& remote
, uint16_t id
)
231 DNSName tsigalgorithm
;
235 if (B
.getDomainMetadata(domain
, "TSIG-ALLOW-AXFR", meta
) && meta
.size() > 0) {
236 tsigkeyname
= DNSName(meta
[0]);
239 vector
<uint8_t> packet
;
240 DNSPacketWriter
pw(packet
, domain
, QType::SOA
, 1, Opcode::Notify
);
241 pw
.getHeader()->id
= id
;
242 pw
.getHeader()->aa
= true;
244 if (tsigkeyname
.empty() == false) {
245 if (!B
.getTSIGKey(tsigkeyname
, &tsigalgorithm
, &tsigsecret64
)) {
246 L
<<Logger::Error
<<"TSIG key '"<<tsigkeyname
<<"' for domain '"<<domain
<<"' not found"<<endl
;
249 TSIGRecordContent trc
;
250 if (tsigalgorithm
.toStringNoDot() == "hmac-md5")
251 trc
.d_algoName
= DNSName(tsigalgorithm
.toStringNoDot() + ".sig-alg.reg.int.");
253 trc
.d_algoName
= tsigalgorithm
;
254 trc
.d_time
= time(0);
256 trc
.d_origID
=ntohs(id
);
258 if (B64Decode(tsigsecret64
, tsigsecret
) == -1) {
259 L
<<Logger::Error
<<"Unable to Base-64 decode TSIG key '"<<tsigkeyname
<<"' for domain '"<<domain
<<"'"<<endl
;
262 addTSIG(pw
, trc
, tsigkeyname
, tsigsecret
, "", false);
265 if(sendto(sock
, &packet
[0], packet
.size(), 0, (struct sockaddr
*)(&remote
), remote
.getSocklen()) < 0) {
266 throw ResolverException("Unable to send notify to "+remote
.toStringWithPort()+": "+stringerror());
270 void CommunicatorClass::drillHole(const DNSName
&domain
, const string
&ip
)
273 d_holes
[make_pair(domain
,ip
)]=time(0);
276 bool CommunicatorClass::justNotified(const DNSName
&domain
, const string
&ip
)
279 if(d_holes
.find(make_pair(domain
,ip
))==d_holes
.end()) // no hole
282 if(d_holes
[make_pair(domain
,ip
)]>time(0)-900) // recent hole
285 // do we want to purge this? XXX FIXME
289 void CommunicatorClass::makeNotifySockets()
291 if(!::arg()["query-local-address"].empty()) {
292 d_nsock4
= makeQuerySocket(ComboAddress(::arg()["query-local-address"]), true, ::arg().mustDo("non-local-bind"));
296 if(!::arg()["query-local-address6"].empty()) {
297 d_nsock6
= makeQuerySocket(ComboAddress(::arg()["query-local-address6"]), true, ::arg().mustDo("non-local-bind"));
303 void CommunicatorClass::notify(const DNSName
&domain
, const string
&ip
)
305 d_nq
.add(domain
, ip
);