]>
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" | |
20829585 | 47 | #include "query-local-address.hh" |
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 | |
0dd42dbb PL |
54 | using pdns::resolver::parseResult; |
55 | ||
f688119e | 56 | int makeQuerySocket(const ComboAddress& local, bool udpOrTCP, bool nonLocalBind) |
12c86877 | 57 | { |
0c01dd7c | 58 | ComboAddress ourLocal(local); |
12c86877 | 59 | |
0c01dd7c | 60 | int sock=socket(ourLocal.sin4.sin_family, udpOrTCP ? SOCK_DGRAM : SOCK_STREAM, 0); |
5e2be4cf | 61 | if(sock < 0) { |
883ad072 | 62 | if(errno == EAFNOSUPPORT && local.sin4.sin_family == AF_INET6) { |
0a462407 | 63 | return -1; |
883ad072 YG |
64 | } |
65 | unixDie("Creating local resolver socket for "+ourLocal.toString()); | |
5e2be4cf | 66 | } |
0c01dd7c | 67 | |
883ad072 | 68 | setCloseOnExec(sock); |
f688119e RG |
69 | |
70 | if(nonLocalBind) | |
71 | Utility::setBindAny(local.sin4.sin_family, sock); | |
72 | ||
eabdf7e0 PD |
73 | if(udpOrTCP) { |
74 | // udp, try hard to bind an unpredictable port | |
0c01dd7c BH |
75 | int tries=10; |
76 | while(--tries) { | |
77 | ourLocal.sin4.sin_port = htons(10000+(dns_random(10000))); | |
78 | ||
79 | if (::bind(sock, (struct sockaddr *)&ourLocal, ourLocal.getSocklen()) >= 0) | |
80 | break; | |
81 | } | |
eabdf7e0 PD |
82 | // cerr<<"bound udp port "<<ourLocal.sin4.sin_port<<", "<<tries<<" tries left"<<endl; |
83 | ||
0c01dd7c | 84 | if(!tries) { |
3897b9e1 | 85 | closesocket(sock); |
3f81d239 | 86 | throw PDNSException("Resolver binding to local UDP socket on "+ourLocal.toString()+": "+stringerror()); |
0c01dd7c | 87 | } |
12c86877 | 88 | } |
0c01dd7c | 89 | else { |
eabdf7e0 | 90 | // tcp, let the kernel figure out the port |
0c01dd7c | 91 | ourLocal.sin4.sin_port = 0; |
f852aff6 | 92 | if(::bind(sock, (struct sockaddr *)&ourLocal, ourLocal.getSocklen()) < 0) { |
93 | closesocket(sock); | |
3f81d239 | 94 | throw PDNSException("Resolver binding to local TCP socket on "+ourLocal.toString()+": "+stringerror()); |
f852aff6 | 95 | } |
eab7dbda | 96 | } |
0c01dd7c | 97 | return sock; |
12c86877 BH |
98 | } |
99 | ||
100 | Resolver::Resolver() | |
101 | { | |
2056e985 CH |
102 | locals["default4"] = -1; |
103 | locals["default6"] = -1; | |
104 | try { | |
20829585 PL |
105 | if (pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) { |
106 | locals["default4"] = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET, 0), true, ::arg().mustDo("non-local-bind")); | |
107 | } | |
108 | if (pdns::isQueryLocalAddressFamilyEnabled(AF_INET6)) { | |
109 | locals["default6"] = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET6, 0), true, ::arg().mustDo("non-local-bind")); | |
110 | } | |
2056e985 CH |
111 | } |
112 | catch(...) { | |
113 | if(locals["default4"]>=0) | |
114 | close(locals["default4"]); | |
9d36589a PL |
115 | if(locals["default6"]>=0) |
116 | close(locals["default6"]); | |
2056e985 CH |
117 | throw; |
118 | } | |
12c86877 BH |
119 | } |
120 | ||
0c01dd7c | 121 | Resolver::~Resolver() |
12c86877 | 122 | { |
93a471a3 CHB |
123 | for (auto& iter: locals) { |
124 | if (iter.second >= 0) | |
125 | close(iter.second); | |
677aa52d | 126 | } |
12c86877 BH |
127 | } |
128 | ||
95302209 | 129 | uint16_t Resolver::sendResolve(const ComboAddress& remote, const ComboAddress& local, |
7f24b6c6 | 130 | const DNSName &domain, int type, int *localsock, bool dnssecOK, |
561434a6 | 131 | const DNSName& tsigkeyname, const DNSName& tsigalgorithm, |
7597b3cf | 132 | const string& tsigsecret) |
12c86877 | 133 | { |
f0136bd5 | 134 | uint16_t randomid; |
88c1bc50 BH |
135 | vector<uint8_t> packet; |
136 | DNSPacketWriter pw(packet, domain, type); | |
a410b176 | 137 | pw.getHeader()->id = randomid = dns_random_uint16(); |
7eb7ac3d | 138 | |
48afcf83 BH |
139 | if(dnssecOK) { |
140 | pw.addOpt(2800, 0, EDNSOpts::DNSSECOK); | |
141 | pw.commit(); | |
142 | } | |
7eb7ac3d | 143 | |
7597b3cf | 144 | if(!tsigkeyname.empty()) { |
842c8dd2 | 145 | // cerr<<"Adding TSIG to notification, key name: '"<<tsigkeyname<<"', algo: '"<<tsigalgorithm<<"', secret: "<<Base64Encode(tsigsecret)<<endl; |
7597b3cf | 146 | TSIGRecordContent trc; |
290a083d | 147 | if (tsigalgorithm == DNSName("hmac-md5")) |
148 | trc.d_algoName = tsigalgorithm + DNSName("sig-alg.reg.int"); | |
a56bc64d AT |
149 | else |
150 | trc.d_algoName = tsigalgorithm; | |
7597b3cf BH |
151 | trc.d_time = time(0); |
152 | trc.d_fudge = 300; | |
f0136bd5 | 153 | trc.d_origID=ntohs(randomid); |
7597b3cf | 154 | trc.d_eRcode=0; |
ea3816cf | 155 | addTSIG(pw, trc, tsigkeyname, tsigsecret, "", false); |
7597b3cf | 156 | } |
7eb7ac3d | 157 | |
95302209 AT |
158 | int sock; |
159 | ||
160 | // choose socket based on local | |
161 | if (local.sin4.sin_family == 0) { | |
7eb7ac3d | 162 | // up to us. |
ea02eeba PL |
163 | if (!pdns::isQueryLocalAddressFamilyEnabled(remote.sin4.sin_family)) { |
164 | string ipv = remote.sin4.sin_family == AF_INET ? "4" : "6"; | |
165 | throw ResolverException("No IPv" + ipv + " socket available, is such an address configured in query-local-address?"); | |
9d36589a | 166 | } |
20829585 | 167 | sock = remote.sin4.sin_family == AF_INET ? locals["default4"] : locals["default6"]; |
95302209 | 168 | } else { |
7eb7ac3d KM |
169 | std::string lstr = local.toString(); |
170 | std::map<std::string, int>::iterator lptr; | |
7eb7ac3d | 171 | |
df2146ee | 172 | // reuse an existing local socket or make a new one |
7eb7ac3d KM |
173 | if ((lptr = locals.find(lstr)) != locals.end()) { |
174 | sock = lptr->second; | |
175 | } else { | |
176 | // try to make socket | |
177 | sock = makeQuerySocket(local, true); | |
b841314c | 178 | if (sock < 0) |
df2146ee | 179 | throw ResolverException("Unable to create local socket on "+lstr+" to "+remote.toStringWithPort()+": "+stringerror()); |
3897b9e1 | 180 | setNonBlocking( sock ); |
7eb7ac3d KM |
181 | locals[lstr] = sock; |
182 | } | |
95302209 | 183 | } |
7eb7ac3d | 184 | |
7f24b6c6 CH |
185 | if (localsock != nullptr) { |
186 | *localsock = sock; | |
187 | } | |
0c01dd7c BH |
188 | if(sendto(sock, &packet[0], packet.size(), 0, (struct sockaddr*)(&remote), remote.getSocklen()) < 0) { |
189 | throw ResolverException("Unable to ask query of "+remote.toStringWithPort()+": "+stringerror()); | |
190 | } | |
f0136bd5 | 191 | return randomid; |
0c01dd7c | 192 | } |
48afcf83 | 193 | |
0dd42dbb PL |
194 | namespace pdns { |
195 | namespace resolver { | |
196 | int parseResult(MOADNSParser& mdp, const DNSName& origQname, uint16_t origQtype, uint16_t id, Resolver::res_t* result) | |
197 | { | |
198 | result->clear(); | |
199 | ||
200 | if(mdp.d_header.rcode) | |
201 | return mdp.d_header.rcode; | |
202 | ||
203 | if(origQname.countLabels()) { // not AXFR | |
204 | if(mdp.d_header.id != id) | |
205 | throw ResolverException("Remote nameserver replied with wrong id"); | |
206 | if(mdp.d_header.qdcount != 1) | |
207 | throw ResolverException("resolver: received answer with wrong number of questions ("+itoa(mdp.d_header.qdcount)+")"); | |
208 | if(mdp.d_qname != origQname) | |
209 | throw ResolverException(string("resolver: received an answer to another question (")+mdp.d_qname.toLogString()+"!="+ origQname.toLogString()+".)"); | |
210 | } | |
1813c5ff | 211 | |
0dd42dbb PL |
212 | vector<DNSResourceRecord> ret; |
213 | DNSResourceRecord rr; | |
214 | result->reserve(mdp.d_answers.size()); | |
dc470c5a | 215 | |
0dd42dbb PL |
216 | for (const auto& i: mdp.d_answers) { |
217 | rr.qname = i.first.d_name; | |
218 | rr.qtype = i.first.d_type; | |
219 | rr.ttl = i.first.d_ttl; | |
220 | rr.content = i.first.d_content->getZoneRepresentation(true); | |
221 | result->push_back(rr); | |
222 | } | |
1813c5ff | 223 | |
0dd42dbb PL |
224 | return 0; |
225 | } | |
226 | ||
227 | } // namespace resolver | |
228 | } // namespace pdns | |
12c86877 | 229 | |
40c9a111 | 230 | bool Resolver::tryGetSOASerial(DNSName *domain, ComboAddress* remote, uint32_t *theirSerial, uint32_t *theirInception, uint32_t *theirExpire, uint16_t* id) |
98e05fce | 231 | { |
9c901967 | 232 | auto fds = std::unique_ptr<struct pollfd[]>(new struct pollfd[locals.size()]); |
95302209 | 233 | size_t i = 0, k; |
0c01dd7c | 234 | int sock; |
95302209 | 235 | |
93a471a3 CHB |
236 | for (const auto& iter: locals) { |
237 | fds[i].fd = iter.second; | |
95302209 | 238 | fds[i].events = POLLIN; |
93a471a3 | 239 | ++i; |
95302209 AT |
240 | } |
241 | ||
9c901967 | 242 | if (poll(fds.get(), i, 250) < 1) { // wait for 0.25s |
7eb7ac3d | 243 | return false; |
95302209 AT |
244 | } |
245 | ||
246 | sock = -1; | |
247 | ||
248 | // determine who | |
249 | for(k=0;k<i;k++) { | |
250 | if ((fds[k].revents & POLLIN) == POLLIN) { | |
251 | sock = fds[k].fd; | |
252 | break; | |
253 | } | |
254 | } | |
255 | ||
95302209 | 256 | if (sock < 0) return false; // false alarm |
7eb7ac3d | 257 | |
3696224d | 258 | int err; |
40c9a111 RG |
259 | remote->sin6.sin6_family = AF_INET6; // make sure getSocklen() below returns a large enough value |
260 | socklen_t addrlen=remote->getSocklen(); | |
0c01dd7c | 261 | char buf[3000]; |
40c9a111 | 262 | err = recvfrom(sock, buf, sizeof(buf), 0,(struct sockaddr*)(remote), &addrlen); |
3696224d BH |
263 | if(err < 0) { |
264 | if(errno == EAGAIN) | |
265 | return false; | |
95302209 | 266 | |
3696224d BH |
267 | throw ResolverException("recvfrom error waiting for answer: "+stringerror()); |
268 | } | |
95302209 | 269 | |
27c0050c | 270 | MOADNSParser mdp(false, (char*)buf, err); |
3696224d | 271 | *id=mdp.d_header.id; |
561434a6 | 272 | *domain = mdp.d_qname; |
3696224d | 273 | |
a36815f4 | 274 | if(domain->empty()) |
40c9a111 | 275 | throw ResolverException("SOA query to '" + remote->toStringWithPort() + "' produced response without domain name (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")"); |
a36815f4 | 276 | |
3696224d | 277 | if(mdp.d_answers.empty()) |
40c9a111 | 278 | throw ResolverException("Query to '" + remote->toStringWithPort() + "' for SOA of '" + domain->toLogString() + "' produced no results (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")"); |
3696224d | 279 | |
48afcf83 | 280 | if(mdp.d_qtype != QType::SOA) |
40c9a111 | 281 | throw ResolverException("Query to '" + remote->toStringWithPort() + "' for SOA of '" + domain->toLogString() + "' returned wrong record type"); |
12c86877 | 282 | |
4d247207 KD |
283 | if(mdp.d_header.rcode != 0) |
284 | throw ResolverException("Query to '" + remote->toStringWithPort() + "' for SOA of '" + domain->toLogString() + "' returned Rcode " + RCode::to_s(mdp.d_header.rcode)); | |
285 | ||
48afcf83 BH |
286 | *theirInception = *theirExpire = 0; |
287 | bool gotSOA=false; | |
ef7cd021 | 288 | for(const MOADNSParser::answers_t::value_type& drc : mdp.d_answers) { |
22880ed9 | 289 | if(drc.first.d_type == QType::SOA && drc.first.d_name == *domain) { |
ba3c54cb RG |
290 | shared_ptr<SOARecordContent> src=getRR<SOARecordContent>(drc.first); |
291 | if (src) { | |
292 | *theirSerial=src->d_st.serial; | |
293 | gotSOA = true; | |
294 | } | |
48afcf83 | 295 | } |
22880ed9 | 296 | if(drc.first.d_type == QType::RRSIG && drc.first.d_name == *domain) { |
ba3c54cb RG |
297 | shared_ptr<RRSIGRecordContent> rrc=getRR<RRSIGRecordContent>(drc.first); |
298 | if(rrc && rrc->d_type == QType::SOA) { | |
232f0877 CH |
299 | *theirInception= std::max(*theirInception, rrc->d_siginception); |
300 | *theirExpire = std::max(*theirExpire, rrc->d_sigexpire); | |
965f9774 | 301 | } |
48afcf83 BH |
302 | } |
303 | } | |
304 | if(!gotSOA) | |
40c9a111 | 305 | throw ResolverException("Query to '" + remote->toString() + "' for SOA of '" + domain->toLogString() + "' did not return a SOA"); |
3696224d BH |
306 | return true; |
307 | } | |
d3491d5a | 308 | |
d622042f | 309 | int Resolver::resolve(const ComboAddress& to, const DNSName &domain, int type, Resolver::res_t* res, const ComboAddress &local) |
3696224d | 310 | { |
98e05fce | 311 | try { |
7f24b6c6 CH |
312 | int sock = -1; |
313 | int id = sendResolve(to, local, domain, type, &sock); | |
7108e055 | 314 | int err=waitForData(sock, 0, 3000000); |
0c01dd7c BH |
315 | |
316 | if(!err) { | |
317 | throw ResolverException("Timeout waiting for answer"); | |
318 | } | |
319 | if(err < 0) | |
320 | throw ResolverException("Error waiting for answer: "+stringerror()); | |
321 | ||
322 | ComboAddress from; | |
323 | socklen_t addrlen = sizeof(from); | |
324 | char buffer[3000]; | |
325 | int len; | |
40c9a111 | 326 | |
0c01dd7c BH |
327 | if((len=recvfrom(sock, buffer, sizeof(buffer), 0,(struct sockaddr*)(&from), &addrlen)) < 0) |
328 | throw ResolverException("recvfrom error waiting for answer: "+stringerror()); | |
40c9a111 RG |
329 | |
330 | if (from != to) { | |
331 | throw ResolverException("Got answer from the wrong peer while resolving ("+from.toStringWithPort()+" instead of "+to.toStringWithPort()+", discarding"); | |
332 | } | |
333 | ||
27c0050c | 334 | MOADNSParser mdp(false, buffer, len); |
ab4994df | 335 | return parseResult(mdp, domain, type, id, res); |
98e05fce BH |
336 | } |
337 | catch(ResolverException &re) { | |
9b0f144f | 338 | throw ResolverException(re.reason+" from "+to.toLogString()); |
98e05fce | 339 | } |
0c01dd7c | 340 | return -1; |
98e05fce | 341 | } |
12c86877 | 342 | |
d622042f | 343 | int Resolver::resolve(const ComboAddress& ipport, const DNSName &domain, int type, Resolver::res_t* res) { |
4a1ab5ed AT |
344 | ComboAddress local; |
345 | local.sin4.sin_family = 0; | |
346 | return resolve(ipport, domain, type, res, local); | |
95302209 | 347 | } |
12c86877 | 348 | |
d622042f | 349 | void Resolver::getSoaSerial(const ComboAddress& ipport, const DNSName &domain, uint32_t *serial) |
0c01dd7c BH |
350 | { |
351 | vector<DNSResourceRecord> res; | |
561434a6 | 352 | int ret = resolve(ipport, domain, QType::SOA, &res); |
3696224d | 353 | |
0c01dd7c | 354 | if(ret || res.empty()) |
9b0f144f | 355 | throw ResolverException("Query to '" + ipport.toLogString() + "' for SOA of '" + domain.toLogString() + "' produced no answers"); |
12c86877 | 356 | |
0c01dd7c | 357 | if(res[0].qtype.getCode() != QType::SOA) |
9b0f144f | 358 | throw ResolverException("Query to '" + ipport.toLogString() + "' for SOA of '" + domain.toLogString() + "' produced a "+res[0].qtype.getName()+" record"); |
0c01dd7c BH |
359 | |
360 | vector<string>parts; | |
361 | stringtok(parts, res[0].content); | |
362 | if(parts.size()<3) | |
9b0f144f | 363 | throw ResolverException("Query to '" + ipport.toLogString() + "' for SOA of '" + domain.toLogString() + "' produced an unparseable response"); |
95dd3b90 RG |
364 | |
365 | try { | |
366 | *serial=pdns_stou(parts[2]); | |
367 | } | |
368 | catch(const std::out_of_range& oor) { | |
9b0f144f | 369 | throw ResolverException("Query to '" + ipport.toLogString() + "' for SOA of '" + domain.toLogString() + "' produced an unparseable serial"); |
95dd3b90 | 370 | } |
12c86877 BH |
371 | } |
372 |