]>
Commit | Line | Data |
---|---|---|
12c86877 | 1 | /* |
6edbf68a 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 | |
731f58b8 | 25 | #include "utility.hh" |
12c86877 BH |
26 | #include "resolver.hh" |
27 | #include <pthread.h> | |
28 | #include <semaphore.h> | |
29 | #include <iostream> | |
30 | #include <errno.h> | |
31 | #include "misc.hh" | |
32 | #include <algorithm> | |
33 | #include <sstream> | |
3696224d | 34 | #include "dnsrecords.hh" |
12c86877 BH |
35 | #include <cstring> |
36 | #include <string> | |
37 | #include <vector> | |
b8e0f341 | 38 | #include <boost/algorithm/string.hpp> |
12c86877 BH |
39 | #include "dns.hh" |
40 | #include "qtype.hh" | |
ef03cc09 | 41 | |
5c409fa2 | 42 | #include "pdnsexception.hh" |
12c86877 | 43 | #include "arguments.hh" |
7597b3cf | 44 | #include "base64.hh" |
88c1bc50 BH |
45 | #include "dnswriter.hh" |
46 | #include "dnsparser.hh" | |
dd7da6cd | 47 | |
fa8fd4d2 | 48 | |
afbc704a | 49 | #include "dns_random.hh" |
d824de38 | 50 | #include <poll.h> |
ef03cc09 | 51 | #include "gss_context.hh" |
eb4e3090 | 52 | #include "namespaces.hh" |
12c86877 | 53 | |
f688119e | 54 | int makeQuerySocket(const ComboAddress& local, bool udpOrTCP, bool nonLocalBind) |
12c86877 | 55 | { |
0c01dd7c | 56 | ComboAddress ourLocal(local); |
12c86877 | 57 | |
0c01dd7c | 58 | int sock=socket(ourLocal.sin4.sin_family, udpOrTCP ? SOCK_DGRAM : SOCK_STREAM, 0); |
5e2be4cf | 59 | if(sock < 0) { |
883ad072 | 60 | if(errno == EAFNOSUPPORT && local.sin4.sin_family == AF_INET6) { |
0a462407 | 61 | return -1; |
883ad072 YG |
62 | } |
63 | unixDie("Creating local resolver socket for "+ourLocal.toString()); | |
5e2be4cf | 64 | } |
0c01dd7c | 65 | |
883ad072 | 66 | setCloseOnExec(sock); |
f688119e RG |
67 | |
68 | if(nonLocalBind) | |
69 | Utility::setBindAny(local.sin4.sin_family, sock); | |
70 | ||
eabdf7e0 PD |
71 | if(udpOrTCP) { |
72 | // udp, try hard to bind an unpredictable port | |
0c01dd7c BH |
73 | int tries=10; |
74 | while(--tries) { | |
75 | ourLocal.sin4.sin_port = htons(10000+(dns_random(10000))); | |
76 | ||
77 | if (::bind(sock, (struct sockaddr *)&ourLocal, ourLocal.getSocklen()) >= 0) | |
78 | break; | |
79 | } | |
eabdf7e0 PD |
80 | // cerr<<"bound udp port "<<ourLocal.sin4.sin_port<<", "<<tries<<" tries left"<<endl; |
81 | ||
0c01dd7c | 82 | if(!tries) { |
3897b9e1 | 83 | closesocket(sock); |
3f81d239 | 84 | throw PDNSException("Resolver binding to local UDP socket on "+ourLocal.toString()+": "+stringerror()); |
0c01dd7c | 85 | } |
12c86877 | 86 | } |
0c01dd7c | 87 | else { |
eabdf7e0 | 88 | // tcp, let the kernel figure out the port |
0c01dd7c | 89 | ourLocal.sin4.sin_port = 0; |
f852aff6 | 90 | if(::bind(sock, (struct sockaddr *)&ourLocal, ourLocal.getSocklen()) < 0) { |
91 | closesocket(sock); | |
3f81d239 | 92 | throw PDNSException("Resolver binding to local TCP socket on "+ourLocal.toString()+": "+stringerror()); |
f852aff6 | 93 | } |
eab7dbda | 94 | } |
0c01dd7c | 95 | return sock; |
12c86877 BH |
96 | } |
97 | ||
98 | Resolver::Resolver() | |
99 | { | |
2056e985 CH |
100 | locals["default4"] = -1; |
101 | locals["default6"] = -1; | |
102 | try { | |
9d36589a PL |
103 | if(!::arg()["query-local-address"].empty()) |
104 | locals["default4"] = makeQuerySocket(ComboAddress(::arg()["query-local-address"]), true, ::arg().mustDo("non-local-bind")); | |
2056e985 | 105 | if(!::arg()["query-local-address6"].empty()) |
f688119e | 106 | locals["default6"] = makeQuerySocket(ComboAddress(::arg()["query-local-address6"]), true, ::arg().mustDo("non-local-bind")); |
2056e985 CH |
107 | } |
108 | catch(...) { | |
109 | if(locals["default4"]>=0) | |
110 | close(locals["default4"]); | |
9d36589a PL |
111 | if(locals["default6"]>=0) |
112 | close(locals["default6"]); | |
2056e985 CH |
113 | throw; |
114 | } | |
12c86877 BH |
115 | } |
116 | ||
0c01dd7c | 117 | Resolver::~Resolver() |
12c86877 | 118 | { |
93a471a3 CHB |
119 | for (auto& iter: locals) { |
120 | if (iter.second >= 0) | |
121 | close(iter.second); | |
677aa52d | 122 | } |
12c86877 BH |
123 | } |
124 | ||
95302209 | 125 | uint16_t Resolver::sendResolve(const ComboAddress& remote, const ComboAddress& local, |
7f24b6c6 | 126 | const DNSName &domain, int type, int *localsock, bool dnssecOK, |
561434a6 | 127 | const DNSName& tsigkeyname, const DNSName& tsigalgorithm, |
7597b3cf | 128 | const string& tsigsecret) |
12c86877 | 129 | { |
f0136bd5 | 130 | uint16_t randomid; |
88c1bc50 BH |
131 | vector<uint8_t> packet; |
132 | DNSPacketWriter pw(packet, domain, type); | |
f0136bd5 | 133 | pw.getHeader()->id = randomid = dns_random(0xffff); |
7eb7ac3d | 134 | |
48afcf83 BH |
135 | if(dnssecOK) { |
136 | pw.addOpt(2800, 0, EDNSOpts::DNSSECOK); | |
137 | pw.commit(); | |
138 | } | |
7eb7ac3d | 139 | |
7597b3cf | 140 | if(!tsigkeyname.empty()) { |
842c8dd2 | 141 | // cerr<<"Adding TSIG to notification, key name: '"<<tsigkeyname<<"', algo: '"<<tsigalgorithm<<"', secret: "<<Base64Encode(tsigsecret)<<endl; |
7597b3cf | 142 | TSIGRecordContent trc; |
290a083d | 143 | if (tsigalgorithm == DNSName("hmac-md5")) |
144 | trc.d_algoName = tsigalgorithm + DNSName("sig-alg.reg.int"); | |
a56bc64d AT |
145 | else |
146 | trc.d_algoName = tsigalgorithm; | |
7597b3cf BH |
147 | trc.d_time = time(0); |
148 | trc.d_fudge = 300; | |
f0136bd5 | 149 | trc.d_origID=ntohs(randomid); |
7597b3cf | 150 | trc.d_eRcode=0; |
ea3816cf | 151 | addTSIG(pw, trc, tsigkeyname, tsigsecret, "", false); |
7597b3cf | 152 | } |
7eb7ac3d | 153 | |
95302209 AT |
154 | int sock; |
155 | ||
156 | // choose socket based on local | |
157 | if (local.sin4.sin_family == 0) { | |
7eb7ac3d KM |
158 | // up to us. |
159 | sock = remote.sin4.sin_family == AF_INET ? locals["default4"] : locals["default6"]; | |
9d36589a PL |
160 | if (sock == -1) { |
161 | string ipv = remote.sin4.sin_family == AF_INET ? "4" : "6"; | |
162 | string qla = remote.sin4.sin_family == AF_INET ? "" : "6"; | |
163 | throw ResolverException("No IPv" + ipv + " socket available, is query-local-address" + qla + " unset?"); | |
164 | } | |
95302209 | 165 | } else { |
7eb7ac3d KM |
166 | std::string lstr = local.toString(); |
167 | std::map<std::string, int>::iterator lptr; | |
7eb7ac3d | 168 | |
df2146ee | 169 | // reuse an existing local socket or make a new one |
7eb7ac3d KM |
170 | if ((lptr = locals.find(lstr)) != locals.end()) { |
171 | sock = lptr->second; | |
172 | } else { | |
173 | // try to make socket | |
174 | sock = makeQuerySocket(local, true); | |
b841314c | 175 | if (sock < 0) |
df2146ee | 176 | throw ResolverException("Unable to create local socket on "+lstr+" to "+remote.toStringWithPort()+": "+stringerror()); |
3897b9e1 | 177 | setNonBlocking( sock ); |
7eb7ac3d KM |
178 | locals[lstr] = sock; |
179 | } | |
95302209 | 180 | } |
7eb7ac3d | 181 | |
7f24b6c6 CH |
182 | if (localsock != nullptr) { |
183 | *localsock = sock; | |
184 | } | |
0c01dd7c BH |
185 | if(sendto(sock, &packet[0], packet.size(), 0, (struct sockaddr*)(&remote), remote.getSocklen()) < 0) { |
186 | throw ResolverException("Unable to ask query of "+remote.toStringWithPort()+": "+stringerror()); | |
187 | } | |
f0136bd5 | 188 | return randomid; |
0c01dd7c | 189 | } |
48afcf83 | 190 | |
561434a6 | 191 | static int parseResult(MOADNSParser& mdp, const DNSName& origQname, uint16_t origQtype, uint16_t id, Resolver::res_t* result) |
0c01dd7c BH |
192 | { |
193 | result->clear(); | |
1813c5ff KM |
194 | |
195 | if(mdp.d_header.rcode) | |
ab4994df | 196 | return mdp.d_header.rcode; |
1813c5ff | 197 | |
561434a6 | 198 | if(origQname.countLabels()) { // not AXFR |
ab4994df | 199 | if(mdp.d_header.id != id) |
0c01dd7c | 200 | throw ResolverException("Remote nameserver replied with wrong id"); |
ab4994df BH |
201 | if(mdp.d_header.qdcount != 1) |
202 | throw ResolverException("resolver: received answer with wrong number of questions ("+itoa(mdp.d_header.qdcount)+")"); | |
561434a6 | 203 | if(mdp.d_qname != origQname) |
86f1af1c | 204 | throw ResolverException(string("resolver: received an answer to another question (")+mdp.d_qname.toLogString()+"!="+ origQname.toLogString()+".)"); |
12c86877 | 205 | } |
1813c5ff | 206 | |
914353ca | 207 | vector<DNSResourceRecord> ret; |
0c01dd7c | 208 | DNSResourceRecord rr; |
dc470c5a RG |
209 | result->reserve(mdp.d_answers.size()); |
210 | ||
93a471a3 CHB |
211 | for (const auto& i: mdp.d_answers) { |
212 | rr.qname = i.first.d_name; | |
213 | rr.qtype = i.first.d_type; | |
214 | rr.ttl = i.first.d_ttl; | |
215 | rr.content = i.first.d_content->getZoneRepresentation(true); | |
0c01dd7c BH |
216 | result->push_back(rr); |
217 | } | |
1813c5ff | 218 | |
0c01dd7c | 219 | return 0; |
98e05fce | 220 | } |
12c86877 | 221 | |
40c9a111 | 222 | bool Resolver::tryGetSOASerial(DNSName *domain, ComboAddress* remote, uint32_t *theirSerial, uint32_t *theirInception, uint32_t *theirExpire, uint16_t* id) |
98e05fce | 223 | { |
9c901967 | 224 | auto fds = std::unique_ptr<struct pollfd[]>(new struct pollfd[locals.size()]); |
95302209 | 225 | size_t i = 0, k; |
0c01dd7c | 226 | int sock; |
95302209 | 227 | |
93a471a3 CHB |
228 | for (const auto& iter: locals) { |
229 | fds[i].fd = iter.second; | |
95302209 | 230 | fds[i].events = POLLIN; |
93a471a3 | 231 | ++i; |
95302209 AT |
232 | } |
233 | ||
9c901967 | 234 | if (poll(fds.get(), i, 250) < 1) { // wait for 0.25s |
7eb7ac3d | 235 | return false; |
95302209 AT |
236 | } |
237 | ||
238 | sock = -1; | |
239 | ||
240 | // determine who | |
241 | for(k=0;k<i;k++) { | |
242 | if ((fds[k].revents & POLLIN) == POLLIN) { | |
243 | sock = fds[k].fd; | |
244 | break; | |
245 | } | |
246 | } | |
247 | ||
95302209 | 248 | if (sock < 0) return false; // false alarm |
7eb7ac3d | 249 | |
3696224d | 250 | int err; |
40c9a111 RG |
251 | remote->sin6.sin6_family = AF_INET6; // make sure getSocklen() below returns a large enough value |
252 | socklen_t addrlen=remote->getSocklen(); | |
0c01dd7c | 253 | char buf[3000]; |
40c9a111 | 254 | err = recvfrom(sock, buf, sizeof(buf), 0,(struct sockaddr*)(remote), &addrlen); |
3696224d BH |
255 | if(err < 0) { |
256 | if(errno == EAGAIN) | |
257 | return false; | |
95302209 | 258 | |
3696224d BH |
259 | throw ResolverException("recvfrom error waiting for answer: "+stringerror()); |
260 | } | |
95302209 | 261 | |
27c0050c | 262 | MOADNSParser mdp(false, (char*)buf, err); |
3696224d | 263 | *id=mdp.d_header.id; |
561434a6 | 264 | *domain = mdp.d_qname; |
3696224d | 265 | |
a36815f4 | 266 | if(domain->empty()) |
40c9a111 | 267 | throw ResolverException("SOA query to '" + remote->toStringWithPort() + "' produced response without domain name (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")"); |
a36815f4 | 268 | |
3696224d | 269 | if(mdp.d_answers.empty()) |
40c9a111 | 270 | throw ResolverException("Query to '" + remote->toStringWithPort() + "' for SOA of '" + domain->toLogString() + "' produced no results (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")"); |
3696224d | 271 | |
48afcf83 | 272 | if(mdp.d_qtype != QType::SOA) |
40c9a111 | 273 | throw ResolverException("Query to '" + remote->toStringWithPort() + "' for SOA of '" + domain->toLogString() + "' returned wrong record type"); |
12c86877 | 274 | |
4d247207 KD |
275 | if(mdp.d_header.rcode != 0) |
276 | throw ResolverException("Query to '" + remote->toStringWithPort() + "' for SOA of '" + domain->toLogString() + "' returned Rcode " + RCode::to_s(mdp.d_header.rcode)); | |
277 | ||
48afcf83 BH |
278 | *theirInception = *theirExpire = 0; |
279 | bool gotSOA=false; | |
ef7cd021 | 280 | for(const MOADNSParser::answers_t::value_type& drc : mdp.d_answers) { |
22880ed9 | 281 | if(drc.first.d_type == QType::SOA && drc.first.d_name == *domain) { |
ba3c54cb RG |
282 | shared_ptr<SOARecordContent> src=getRR<SOARecordContent>(drc.first); |
283 | if (src) { | |
284 | *theirSerial=src->d_st.serial; | |
285 | gotSOA = true; | |
286 | } | |
48afcf83 | 287 | } |
22880ed9 | 288 | if(drc.first.d_type == QType::RRSIG && drc.first.d_name == *domain) { |
ba3c54cb RG |
289 | shared_ptr<RRSIGRecordContent> rrc=getRR<RRSIGRecordContent>(drc.first); |
290 | if(rrc && rrc->d_type == QType::SOA) { | |
232f0877 CH |
291 | *theirInception= std::max(*theirInception, rrc->d_siginception); |
292 | *theirExpire = std::max(*theirExpire, rrc->d_sigexpire); | |
965f9774 | 293 | } |
48afcf83 BH |
294 | } |
295 | } | |
296 | if(!gotSOA) | |
40c9a111 | 297 | throw ResolverException("Query to '" + remote->toString() + "' for SOA of '" + domain->toLogString() + "' did not return a SOA"); |
3696224d BH |
298 | return true; |
299 | } | |
d3491d5a | 300 | |
d622042f | 301 | int Resolver::resolve(const ComboAddress& to, const DNSName &domain, int type, Resolver::res_t* res, const ComboAddress &local) |
3696224d | 302 | { |
98e05fce | 303 | try { |
7f24b6c6 CH |
304 | int sock = -1; |
305 | int id = sendResolve(to, local, domain, type, &sock); | |
7108e055 | 306 | int err=waitForData(sock, 0, 3000000); |
0c01dd7c BH |
307 | |
308 | if(!err) { | |
309 | throw ResolverException("Timeout waiting for answer"); | |
310 | } | |
311 | if(err < 0) | |
312 | throw ResolverException("Error waiting for answer: "+stringerror()); | |
313 | ||
314 | ComboAddress from; | |
315 | socklen_t addrlen = sizeof(from); | |
316 | char buffer[3000]; | |
317 | int len; | |
40c9a111 | 318 | |
0c01dd7c BH |
319 | if((len=recvfrom(sock, buffer, sizeof(buffer), 0,(struct sockaddr*)(&from), &addrlen)) < 0) |
320 | throw ResolverException("recvfrom error waiting for answer: "+stringerror()); | |
40c9a111 RG |
321 | |
322 | if (from != to) { | |
323 | throw ResolverException("Got answer from the wrong peer while resolving ("+from.toStringWithPort()+" instead of "+to.toStringWithPort()+", discarding"); | |
324 | } | |
325 | ||
27c0050c | 326 | MOADNSParser mdp(false, buffer, len); |
ab4994df | 327 | return parseResult(mdp, domain, type, id, res); |
98e05fce BH |
328 | } |
329 | catch(ResolverException &re) { | |
9b0f144f | 330 | throw ResolverException(re.reason+" from "+to.toLogString()); |
98e05fce | 331 | } |
0c01dd7c | 332 | return -1; |
98e05fce | 333 | } |
12c86877 | 334 | |
d622042f | 335 | int Resolver::resolve(const ComboAddress& ipport, const DNSName &domain, int type, Resolver::res_t* res) { |
4a1ab5ed AT |
336 | ComboAddress local; |
337 | local.sin4.sin_family = 0; | |
338 | return resolve(ipport, domain, type, res, local); | |
95302209 | 339 | } |
12c86877 | 340 | |
d622042f | 341 | void Resolver::getSoaSerial(const ComboAddress& ipport, const DNSName &domain, uint32_t *serial) |
0c01dd7c BH |
342 | { |
343 | vector<DNSResourceRecord> res; | |
561434a6 | 344 | int ret = resolve(ipport, domain, QType::SOA, &res); |
3696224d | 345 | |
0c01dd7c | 346 | if(ret || res.empty()) |
9b0f144f | 347 | throw ResolverException("Query to '" + ipport.toLogString() + "' for SOA of '" + domain.toLogString() + "' produced no answers"); |
12c86877 | 348 | |
0c01dd7c | 349 | if(res[0].qtype.getCode() != QType::SOA) |
9b0f144f | 350 | throw ResolverException("Query to '" + ipport.toLogString() + "' for SOA of '" + domain.toLogString() + "' produced a "+res[0].qtype.getName()+" record"); |
0c01dd7c BH |
351 | |
352 | vector<string>parts; | |
353 | stringtok(parts, res[0].content); | |
354 | if(parts.size()<3) | |
9b0f144f | 355 | throw ResolverException("Query to '" + ipport.toLogString() + "' for SOA of '" + domain.toLogString() + "' produced an unparseable response"); |
95dd3b90 RG |
356 | |
357 | try { | |
358 | *serial=pdns_stou(parts[2]); | |
359 | } | |
360 | catch(const std::out_of_range& oor) { | |
9b0f144f | 361 | throw ResolverException("Query to '" + ipport.toLogString() + "' for SOA of '" + domain.toLogString() + "' produced an unparseable serial"); |
95dd3b90 | 362 | } |
12c86877 BH |
363 | } |
364 | ||
fc396d56 | 365 | AXFRRetriever::AXFRRetriever(const ComboAddress& remote, |
98c9ec39 | 366 | const DNSName& domain, |
367 | const TSIGTriplet& tt, | |
db8f9152 | 368 | const ComboAddress* laddr, |
e07c3801 RG |
369 | size_t maxReceivedBytes, |
370 | uint16_t timeout) | |
60a1c204 | 371 | : d_tsigVerifier(tt, remote, d_trc), d_receivedBytes(0), d_maxReceivedBytes(maxReceivedBytes) |
12c86877 | 372 | { |
0c01dd7c | 373 | ComboAddress local; |
9d36589a PL |
374 | if (laddr != nullptr) { |
375 | local = ComboAddress(*laddr); | |
fc396d56 | 376 | } else { |
ee5a6a73 PL |
377 | string qlas = remote.sin4.sin_family == AF_INET ? "query-local-address" : "query-local-address6"; |
378 | if (::arg()[qlas].empty()) { | |
379 | throw ResolverException("Unable to determine source address for AXFR request to " + remote.toStringWithPort() + " for " + domain.toLogString() + ". " + qlas + " is unset"); | |
9d36589a | 380 | } |
ee5a6a73 | 381 | local=ComboAddress(::arg()[qlas]); |
fc396d56 | 382 | } |
3ba05fa7 BH |
383 | d_sock = -1; |
384 | try { | |
385 | d_sock = makeQuerySocket(local, false); // make a TCP socket | |
b841314c RG |
386 | if (d_sock < 0) |
387 | throw ResolverException("Error creating socket for AXFR request to "+d_remote.toStringWithPort()); | |
3ba05fa7 BH |
388 | d_buf = shared_array<char>(new char[65536]); |
389 | d_remote = remote; // mostly for error reporting | |
e07c3801 | 390 | this->connect(timeout); |
3ba05fa7 BH |
391 | d_soacount = 0; |
392 | ||
393 | vector<uint8_t> packet; | |
394 | DNSPacketWriter pw(packet, domain, QType::AXFR); | |
395 | pw.getHeader()->id = dns_random(0xffff); | |
396 | ||
98c9ec39 | 397 | if(!tt.name.empty()) { |
398 | if (tt.algo == DNSName("hmac-md5")) | |
399 | d_trc.d_algoName = tt.algo + DNSName("sig-alg.reg.int"); | |
a56bc64d | 400 | else |
98c9ec39 | 401 | d_trc.d_algoName = tt.algo; |
3ba05fa7 BH |
402 | d_trc.d_time = time(0); |
403 | d_trc.d_fudge = 300; | |
404 | d_trc.d_origID=ntohs(pw.getHeader()->id); | |
405 | d_trc.d_eRcode=0; | |
ea3816cf | 406 | addTSIG(pw, d_trc, tt.name, tt.secret, "", false); |
3ba05fa7 BH |
407 | } |
408 | ||
409 | uint16_t replen=htons(packet.size()); | |
410 | Utility::iovec iov[2]; | |
72a93ecc | 411 | iov[0].iov_base=reinterpret_cast<char*>(&replen); |
3ba05fa7 | 412 | iov[0].iov_len=2; |
72a93ecc | 413 | iov[1].iov_base=packet.data(); |
3ba05fa7 BH |
414 | iov[1].iov_len=packet.size(); |
415 | ||
5b6d751e BH |
416 | int ret=Utility::writev(d_sock, iov, 2); |
417 | if(ret < 0) | |
3ba05fa7 | 418 | throw ResolverException("Error sending question to "+d_remote.toStringWithPort()+": "+stringerror()); |
5b6d751e BH |
419 | if(ret != (int)(2+packet.size())) { |
420 | throw ResolverException("Partial write on AXFR request to "+d_remote.toStringWithPort()); | |
421 | } | |
3ba05fa7 | 422 | |
e07c3801 | 423 | int res = waitForData(d_sock, timeout, 0); |
0c01dd7c | 424 | |
3ba05fa7 BH |
425 | if(!res) |
426 | throw ResolverException("Timeout waiting for answer from "+d_remote.toStringWithPort()+" during AXFR"); | |
427 | if(res<0) | |
428 | throw ResolverException("Error waiting for answer from "+d_remote.toStringWithPort()+": "+stringerror()); | |
429 | } | |
430 | catch(...) { | |
431 | if(d_sock >= 0) | |
432 | close(d_sock); | |
b841314c | 433 | d_sock = -1; |
3ba05fa7 | 434 | throw; |
29b92d6f | 435 | } |
12c86877 BH |
436 | } |
437 | ||
2577467d BH |
438 | AXFRRetriever::~AXFRRetriever() |
439 | { | |
440 | close(d_sock); | |
441 | } | |
12c86877 | 442 | |
54d84273 PD |
443 | |
444 | ||
99bea744 | 445 | int AXFRRetriever::getChunk(Resolver::res_t &res, vector<DNSRecord>* records, uint16_t timeout) // Implementation is making sure RFC2845 4.4 is followed. |
12c86877 | 446 | { |
0c01dd7c BH |
447 | if(d_soacount > 1) |
448 | return false; | |
236e0c78 | 449 | |
12c86877 | 450 | // d_sock is connected and is about to spit out a packet |
b726c127 | 451 | int len=getLength(timeout); |
12c86877 | 452 | if(len<0) |
25b1d5d7 | 453 | throw ResolverException("EOF trying to read axfr chunk from remote TCP client"); |
db8f9152 RG |
454 | |
455 | if (d_maxReceivedBytes > 0 && (d_maxReceivedBytes - d_receivedBytes) < (size_t) len) | |
456 | throw ResolverException("Reached the maximum number of received bytes during AXFR"); | |
457 | ||
99bea744 | 458 | timeoutReadn(len, timeout); |
db8f9152 RG |
459 | |
460 | d_receivedBytes += (uint16_t) len; | |
461 | ||
27c0050c | 462 | MOADNSParser mdp(false, d_buf.get(), len); |
236e0c78 | 463 | |
dc470c5a RG |
464 | int err = mdp.d_header.rcode; |
465 | ||
466 | if(err) { | |
bf491f9b | 467 | throw ResolverException("AXFR chunk error: " + RCode::to_s(err)); |
dc470c5a | 468 | } |
236e0c78 | 469 | |
60a1c204 RG |
470 | try { |
471 | d_tsigVerifier.check(std::string(d_buf.get(), len), mdp); | |
54d84273 | 472 | } |
60a1c204 RG |
473 | catch(const std::runtime_error& re) { |
474 | throw ResolverException(re.what()); | |
475 | } | |
476 | ||
dc470c5a RG |
477 | if(!records) { |
478 | err = parseResult(mdp, DNSName(), 0, 0, &res); | |
479 | ||
480 | if (!err) { | |
481 | for(const auto& answer : mdp.d_answers) | |
482 | if (answer.first.d_type == QType::SOA) | |
483 | d_soacount++; | |
484 | } | |
485 | } | |
486 | else { | |
487 | records->clear(); | |
488 | records->reserve(mdp.d_answers.size()); | |
489 | ||
490 | for(auto& r: mdp.d_answers) { | |
491 | if (r.first.d_type == QType::SOA) { | |
492 | d_soacount++; | |
493 | } | |
494 | ||
495 | records->push_back(std::move(r.first)); | |
496 | } | |
497 | } | |
498 | ||
0c01dd7c BH |
499 | return true; |
500 | } | |
12c86877 | 501 | |
99bea744 | 502 | void AXFRRetriever::timeoutReadn(uint16_t bytes, uint16_t timeoutsec) |
0c01dd7c | 503 | { |
99bea744 | 504 | time_t start=time(nullptr); |
0c01dd7c BH |
505 | int n=0; |
506 | int numread; | |
507 | while(n<bytes) { | |
99bea744 | 508 | int res=waitForData(d_sock, timeoutsec-(time(nullptr)-start)); |
016a0abb | 509 | if(res<0) |
0c01dd7c | 510 | throw ResolverException("Reading data from remote nameserver over TCP: "+stringerror()); |
016a0abb PD |
511 | if(!res) |
512 | throw ResolverException("Timeout while reading data from remote nameserver over TCP"); | |
0c01dd7c BH |
513 | |
514 | numread=recv(d_sock, d_buf.get()+n, bytes-n, 0); | |
515 | if(numread<0) | |
516 | throw ResolverException("Reading data from remote nameserver over TCP: "+stringerror()); | |
517 | if(numread==0) | |
518 | throw ResolverException("Remote nameserver closed TCP connection"); | |
519 | n+=numread; | |
520 | } | |
12c86877 BH |
521 | } |
522 | ||
e07c3801 | 523 | void AXFRRetriever::connect(uint16_t timeout) |
12c86877 | 524 | { |
3897b9e1 | 525 | setNonBlocking( d_sock ); |
0c01dd7c BH |
526 | |
527 | int err; | |
528 | ||
529 | if((err=::connect(d_sock,(struct sockaddr*)&d_remote, d_remote.getSocklen()))<0 && errno!=EINPROGRESS) { | |
a7b68ae7 RG |
530 | try { |
531 | closesocket(d_sock); | |
532 | } | |
533 | catch(const PDNSException& e) { | |
534 | d_sock=-1; | |
535 | throw ResolverException("Error closing AXFR socket after connect() failed: "+e.reason); | |
536 | } | |
537 | ||
0c01dd7c | 538 | throw ResolverException("connect: "+stringerror()); |
88c1bc50 | 539 | } |
0c01dd7c BH |
540 | |
541 | if(!err) | |
542 | goto done; | |
543 | ||
e07c3801 | 544 | err=waitForRWData(d_sock, false, timeout, 0); // wait for writeability |
0c01dd7c BH |
545 | |
546 | if(!err) { | |
a7b68ae7 RG |
547 | try { |
548 | closesocket(d_sock); // timeout | |
549 | } | |
550 | catch(const PDNSException& e) { | |
551 | d_sock=-1; | |
552 | throw ResolverException("Error closing AXFR socket after timeout: "+e.reason); | |
553 | } | |
554 | ||
0c01dd7c BH |
555 | d_sock=-1; |
556 | errno=ETIMEDOUT; | |
557 | ||
558 | throw ResolverException("Timeout connecting to server"); | |
88c1bc50 | 559 | } |
0c01dd7c | 560 | else if(err < 0) { |
e07c3801 | 561 | throw ResolverException("Error connecting: "+string(strerror(errno))); |
849fde0b | 562 | } |
0c01dd7c BH |
563 | else { |
564 | Utility::socklen_t len=sizeof(err); | |
565 | if(getsockopt(d_sock, SOL_SOCKET,SO_ERROR,(char *)&err,&len)<0) | |
566 | throw ResolverException("Error connecting: "+stringerror()); // Solaris | |
567 | ||
568 | if(err) | |
569 | throw ResolverException("Error connecting: "+string(strerror(err))); | |
8b3cfcd3 | 570 | } |
0c01dd7c BH |
571 | |
572 | done: | |
3897b9e1 | 573 | setBlocking( d_sock ); |
0c01dd7c | 574 | // d_sock now connected |
12c86877 BH |
575 | } |
576 | ||
b726c127 | 577 | int AXFRRetriever::getLength(uint16_t timeout) |
98e05fce | 578 | { |
b726c127 | 579 | timeoutReadn(2, timeout); |
0c01dd7c | 580 | return (unsigned char)d_buf[0]*256+(unsigned char)d_buf[1]; |
12c86877 BH |
581 | } |
582 |