]>
Commit | Line | Data |
---|---|---|
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 "utility.hh" | |
26 | #include "lwres.hh" | |
27 | #include <iostream> | |
28 | #include "dnsrecords.hh" | |
29 | #include <errno.h> | |
30 | #include "misc.hh" | |
31 | #include <algorithm> | |
32 | #include <sstream> | |
33 | #include <cstring> | |
34 | #include <string> | |
35 | #include <vector> | |
36 | #include "dns.hh" | |
37 | #include "qtype.hh" | |
38 | #include "pdnsexception.hh" | |
39 | #include "arguments.hh" | |
40 | #include "sstuff.hh" | |
41 | #include "syncres.hh" | |
42 | #include "dnswriter.hh" | |
43 | #include "dnsparser.hh" | |
44 | #include "logger.hh" | |
45 | #include "dns_random.hh" | |
46 | #include <boost/scoped_array.hpp> | |
47 | #include <boost/algorithm/string.hpp> | |
48 | #include "validate-recursor.hh" | |
49 | #include "ednssubnet.hh" | |
50 | ||
51 | #ifdef HAVE_PROTOBUF | |
52 | ||
53 | #include "uuid-utils.hh" | |
54 | ||
55 | #ifdef HAVE_FSTRM | |
56 | #include "rec-dnstap.hh" | |
57 | #include "fstrm_logger.hh" | |
58 | bool g_syslog; | |
59 | ||
60 | static bool isEnabledForQueries(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers) | |
61 | { | |
62 | if (fstreamLoggers == nullptr) { | |
63 | return false; | |
64 | } | |
65 | for (auto& logger : *fstreamLoggers) { | |
66 | if (logger->logQueries()) { | |
67 | return true; | |
68 | } | |
69 | } | |
70 | return false; | |
71 | } | |
72 | ||
73 | static void logFstreamQuery(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers, const struct timeval &queryTime, const ComboAddress& ip, bool doTCP, | |
74 | boost::optional<const DNSName&> auth, const vector<uint8_t>& packet) | |
75 | { | |
76 | if (fstreamLoggers == nullptr) | |
77 | return; | |
78 | ||
79 | struct timespec ts; | |
80 | TIMEVAL_TO_TIMESPEC(&queryTime, &ts); | |
81 | RecDnstapMessage message(SyncRes::s_serverID, nullptr, &ip, doTCP, auth, reinterpret_cast<const char*>(&*packet.begin()), packet.size(), &ts, nullptr); | |
82 | std::string str; | |
83 | message.serialize(str); | |
84 | ||
85 | for (auto& logger : *fstreamLoggers) { | |
86 | logger->queueData(str); | |
87 | } | |
88 | } | |
89 | ||
90 | static bool isEnabledForResponses(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers) | |
91 | { | |
92 | if (fstreamLoggers == nullptr) { | |
93 | return false; | |
94 | } | |
95 | for (auto& logger : *fstreamLoggers) { | |
96 | if (logger->logResponses()) { | |
97 | return true; | |
98 | } | |
99 | } | |
100 | return false; | |
101 | } | |
102 | ||
103 | static void logFstreamResponse(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers, const ComboAddress& ip, bool doTCP, | |
104 | boost::optional<const DNSName&> auth, const std::string& packet, const struct timeval& queryTime, const struct timeval& replyTime) | |
105 | { | |
106 | if (fstreamLoggers == nullptr) | |
107 | return; | |
108 | ||
109 | struct timespec ts1, ts2; | |
110 | TIMEVAL_TO_TIMESPEC(&queryTime, &ts1); | |
111 | TIMEVAL_TO_TIMESPEC(&replyTime, &ts2); | |
112 | RecDnstapMessage message(SyncRes::s_serverID, nullptr, &ip, doTCP, auth, static_cast<const char*>(&*packet.begin()), packet.size(), &ts1, &ts2); | |
113 | std::string str; | |
114 | message.serialize(str); | |
115 | ||
116 | for (auto& logger : *fstreamLoggers) { | |
117 | logger->queueData(str); | |
118 | } | |
119 | } | |
120 | ||
121 | #endif // HAVE_FSTRM | |
122 | ||
123 | static void logOutgoingQuery(const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, boost::optional<RecProtoBufMessage>& message, boost::optional<const boost::uuids::uuid&> initialRequestId, const boost::uuids::uuid& uuid, const ComboAddress& ip, const DNSName& domain, int type, uint16_t qid, bool doTCP, size_t bytes, boost::optional<Netmask>& srcmask) | |
124 | { | |
125 | if (!outgoingLoggers) { | |
126 | return; | |
127 | } | |
128 | ||
129 | bool log = false; | |
130 | for (auto& logger : *outgoingLoggers) { | |
131 | if (logger->logQueries()) { | |
132 | log = true; | |
133 | break; | |
134 | } | |
135 | } | |
136 | ||
137 | if (!log) { | |
138 | return; | |
139 | } | |
140 | ||
141 | message = RecProtoBufMessage(DNSProtoBufMessage::OutgoingQuery, uuid, nullptr, &ip, domain, type, QClass::IN, qid, doTCP, bytes); | |
142 | message->setServerIdentity(SyncRes::s_serverID); | |
143 | ||
144 | if (initialRequestId) { | |
145 | message->setInitialRequestID(*initialRequestId); | |
146 | } | |
147 | ||
148 | if (srcmask) { | |
149 | message->setEDNSSubnet(*srcmask); | |
150 | } | |
151 | ||
152 | // cerr <<message.toDebugString()<<endl; | |
153 | std::string str; | |
154 | message->serialize(str); | |
155 | ||
156 | for (auto& logger : *outgoingLoggers) { | |
157 | if (logger->logQueries()) { | |
158 | logger->queueData(str); | |
159 | } | |
160 | } | |
161 | } | |
162 | ||
163 | static void logIncomingResponse(const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, boost::optional<RecProtoBufMessage>& message, boost::optional<const boost::uuids::uuid&> initialRequestId, const boost::uuids::uuid& uuid, const ComboAddress& ip, const DNSName& domain, int type, uint16_t qid, bool doTCP, boost::optional<Netmask>& srcmask, size_t bytes, int rcode, const std::vector<DNSRecord>& records, const struct timeval& queryTime, const std::set<uint16_t>& exportTypes) | |
164 | { | |
165 | if (!outgoingLoggers) { | |
166 | return; | |
167 | } | |
168 | ||
169 | bool log = false; | |
170 | for (auto& logger : *outgoingLoggers) { | |
171 | if (logger->logResponses()) { | |
172 | log = true; | |
173 | break; | |
174 | } | |
175 | } | |
176 | ||
177 | if (!log) { | |
178 | return; | |
179 | } | |
180 | ||
181 | if (!message) { | |
182 | message = RecProtoBufMessage(DNSProtoBufMessage::IncomingResponse, uuid, nullptr, &ip, domain, type, QClass::IN, qid, doTCP, bytes); | |
183 | message->setServerIdentity(SyncRes::s_serverID); | |
184 | ||
185 | if (initialRequestId) { | |
186 | message->setInitialRequestID(*initialRequestId); | |
187 | } | |
188 | ||
189 | if (srcmask) { | |
190 | message->setEDNSSubnet(*srcmask); | |
191 | } | |
192 | } | |
193 | else { | |
194 | message->updateTime(); | |
195 | message->setType(DNSProtoBufMessage::IncomingResponse); | |
196 | message->setBytes(bytes); | |
197 | } | |
198 | ||
199 | message->setQueryTime(queryTime.tv_sec, queryTime.tv_usec); | |
200 | if (rcode == -1) { | |
201 | message->setNetworkErrorResponseCode(); | |
202 | } | |
203 | else { | |
204 | message->setResponseCode(rcode); | |
205 | } | |
206 | message->addRRs(records, exportTypes); | |
207 | ||
208 | // cerr <<message.toDebugString()<<endl; | |
209 | std::string str; | |
210 | message->serialize(str); | |
211 | ||
212 | for (auto& logger : *outgoingLoggers) { | |
213 | if (logger->logResponses()) { | |
214 | logger->queueData(str); | |
215 | } | |
216 | } | |
217 | } | |
218 | #endif /* HAVE_PROTOBUF */ | |
219 | ||
220 | //! returns -2 for OS limits error, -1 for permanent error that has to do with remote **transport**, 0 for timeout, 1 for success | |
221 | /** lwr is only filled out in case 1 was returned, and even when returning 1 for 'success', lwr might contain DNS errors | |
222 | Never throws! | |
223 | */ | |
224 | int asyncresolve(const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult *lwr, bool* chained) | |
225 | { | |
226 | size_t len; | |
227 | size_t bufsize=g_outgoingEDNSBufsize; | |
228 | std::string buf; | |
229 | buf.resize(bufsize); | |
230 | vector<uint8_t> vpacket; | |
231 | // string mapped0x20=dns0x20(domain); | |
232 | uint16_t qid = dns_random(0xffff); | |
233 | DNSPacketWriter pw(vpacket, domain, type); | |
234 | ||
235 | pw.getHeader()->rd=sendRDQuery; | |
236 | pw.getHeader()->id=qid; | |
237 | /* RFC 6840 section 5.9: | |
238 | * This document further specifies that validating resolvers SHOULD set | |
239 | * the CD bit on every upstream query. This is regardless of whether | |
240 | * the CD bit was set on the incoming query [...] | |
241 | * | |
242 | * sendRDQuery is only true if the qname is part of a forward-zone-recurse (or | |
243 | * set in the forward-zone-file), so we use this as an indicator for it being | |
244 | * an "upstream query". To stay true to "dnssec=off means 3.X behaviour", we | |
245 | * only set +CD on forwarded query in any mode other than dnssec=off. | |
246 | */ | |
247 | pw.getHeader()->cd=(sendRDQuery && g_dnssecmode != DNSSECMode::Off); | |
248 | ||
249 | string ping; | |
250 | bool weWantEDNSSubnet=false; | |
251 | uint8_t outgoingECSBits = 0; | |
252 | ComboAddress outgoingECSAddr; | |
253 | if(EDNS0Level > 0) { | |
254 | DNSPacketWriter::optvect_t opts; | |
255 | if(srcmask) { | |
256 | EDNSSubnetOpts eo; | |
257 | eo.source = *srcmask; | |
258 | outgoingECSBits = srcmask->getBits(); | |
259 | outgoingECSAddr = srcmask->getNetwork(); | |
260 | // cout<<"Adding request mask: "<<eo.source.toString()<<endl; | |
261 | opts.push_back(make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(eo))); | |
262 | weWantEDNSSubnet=true; | |
263 | } | |
264 | ||
265 | pw.addOpt(g_outgoingEDNSBufsize, 0, g_dnssecmode == DNSSECMode::Off ? 0 : EDNSOpts::DNSSECOK, opts); | |
266 | pw.commit(); | |
267 | } | |
268 | lwr->d_rcode = 0; | |
269 | lwr->d_haveEDNS = false; | |
270 | int ret; | |
271 | ||
272 | DTime dt; | |
273 | dt.set(); | |
274 | *now=dt.getTimeval(); | |
275 | ||
276 | #ifdef HAVE_PROTOBUF | |
277 | boost::uuids::uuid uuid; | |
278 | const struct timeval queryTime = *now; | |
279 | boost::optional<RecProtoBufMessage> pbMessage = boost::none; | |
280 | ||
281 | if (outgoingLoggers) { | |
282 | uuid = getUniqueID(); | |
283 | logOutgoingQuery(outgoingLoggers, pbMessage, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, vpacket.size(), srcmask); | |
284 | } | |
285 | #endif /* HAVE_PROTOBUF */ | |
286 | #ifdef HAVE_FSTRM | |
287 | if (isEnabledForQueries(fstrmLoggers)) { | |
288 | logFstreamQuery(fstrmLoggers, queryTime, ip, doTCP, context ? context->d_auth : boost::none, vpacket); | |
289 | } | |
290 | #endif /* HAVE_FSTRM */ | |
291 | ||
292 | srcmask = boost::none; // this is also our return value, even if EDNS0Level == 0 | |
293 | ||
294 | if(!doTCP) { | |
295 | int queryfd; | |
296 | if(ip.sin4.sin_family==AF_INET6) | |
297 | g_stats.ipv6queries++; | |
298 | ||
299 | if((ret=asendto((const char*)&*vpacket.begin(), vpacket.size(), 0, ip, qid, | |
300 | domain, type, &queryfd)) < 0) { | |
301 | return ret; // passes back the -2 EMFILE | |
302 | } | |
303 | ||
304 | if (queryfd == -1) { | |
305 | *chained = true; | |
306 | } | |
307 | ||
308 | // sleep until we see an answer to this, interface to mtasker | |
309 | ||
310 | ret=arecvfrom(buf, 0, ip, &len, qid, | |
311 | domain, type, queryfd, now); | |
312 | } | |
313 | else { | |
314 | try { | |
315 | Socket s(ip.sin4.sin_family, SOCK_STREAM); | |
316 | ||
317 | s.setNonBlocking(); | |
318 | ComboAddress local = getQueryLocalAddress(ip.sin4.sin_family, 0); | |
319 | ||
320 | s.bind(local); | |
321 | ||
322 | s.connect(ip); | |
323 | ||
324 | uint16_t tlen=htons(vpacket.size()); | |
325 | char *lenP=(char*)&tlen; | |
326 | const char *msgP=(const char*)&*vpacket.begin(); | |
327 | string packet=string(lenP, lenP+2)+string(msgP, msgP+vpacket.size()); | |
328 | ||
329 | ret=asendtcp(packet, &s); | |
330 | if(!(ret>0)) | |
331 | return ret; | |
332 | ||
333 | packet.clear(); | |
334 | ret=arecvtcp(packet, 2, &s, false); | |
335 | if(!(ret > 0)) | |
336 | return ret; | |
337 | ||
338 | memcpy(&tlen, packet.c_str(), sizeof(tlen)); | |
339 | len=ntohs(tlen); // switch to the 'len' shared with the rest of the function | |
340 | ||
341 | ret=arecvtcp(packet, len, &s, false); | |
342 | if(!(ret > 0)) | |
343 | return ret; | |
344 | ||
345 | buf.resize(len); | |
346 | memcpy(const_cast<char*>(buf.data()), packet.c_str(), len); | |
347 | ||
348 | ret=1; | |
349 | } | |
350 | catch(NetworkError& ne) { | |
351 | ret = -2; // OS limits error | |
352 | } | |
353 | } | |
354 | ||
355 | ||
356 | lwr->d_usec=dt.udiff(); | |
357 | *now=dt.getTimeval(); | |
358 | ||
359 | if(ret <= 0) { // includes 'timeout' | |
360 | #ifdef HAVE_PROTOBUF | |
361 | if (outgoingLoggers) { | |
362 | logIncomingResponse(outgoingLoggers, pbMessage, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, srcmask, 0, -1, {}, queryTime, exportTypes); | |
363 | } | |
364 | #endif | |
365 | return ret; | |
366 | } | |
367 | ||
368 | buf.resize(len); | |
369 | ||
370 | #ifdef HAVE_FSTRM | |
371 | if (isEnabledForResponses(fstrmLoggers)) { | |
372 | logFstreamResponse(fstrmLoggers, ip, doTCP, context ? context->d_auth : boost::none, buf, queryTime, *now); | |
373 | } | |
374 | #endif /* HAVE_FSTRM */ | |
375 | ||
376 | lwr->d_records.clear(); | |
377 | try { | |
378 | lwr->d_tcbit=0; | |
379 | MOADNSParser mdp(false, buf); | |
380 | lwr->d_aabit=mdp.d_header.aa; | |
381 | lwr->d_tcbit=mdp.d_header.tc; | |
382 | lwr->d_rcode=mdp.d_header.rcode; | |
383 | ||
384 | if(mdp.d_header.rcode == RCode::FormErr && mdp.d_qname.empty() && mdp.d_qtype == 0 && mdp.d_qclass == 0) { | |
385 | #ifdef HAVE_PROTOBUF | |
386 | if(outgoingLoggers) { | |
387 | logIncomingResponse(outgoingLoggers, pbMessage, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, srcmask, len, lwr->d_rcode, lwr->d_records, queryTime, exportTypes); | |
388 | } | |
389 | #endif | |
390 | lwr->d_validpacket=true; | |
391 | return 1; // this is "success", the error is set in lwr->d_rcode | |
392 | } | |
393 | ||
394 | if(domain != mdp.d_qname) { | |
395 | if(!mdp.d_qname.empty() && domain.toString().find((char)0) == string::npos /* ugly */) {// embedded nulls are too noisy, plus empty domains are too | |
396 | g_log<<Logger::Notice<<"Packet purporting to come from remote server "<<ip.toString()<<" contained wrong answer: '" << domain << "' != '" << mdp.d_qname << "'" << endl; | |
397 | } | |
398 | // unexpected count has already been done @ pdns_recursor.cc | |
399 | goto out; | |
400 | } | |
401 | ||
402 | lwr->d_records.reserve(mdp.d_answers.size()); | |
403 | for(const auto& a : mdp.d_answers) | |
404 | lwr->d_records.push_back(a.first); | |
405 | ||
406 | EDNSOpts edo; | |
407 | if(EDNS0Level > 0 && getEDNSOpts(mdp, &edo)) { | |
408 | lwr->d_haveEDNS = true; | |
409 | ||
410 | if(weWantEDNSSubnet) { | |
411 | for(const auto& opt : edo.d_options) { | |
412 | if(opt.first==EDNSOptionCode::ECS) { | |
413 | EDNSSubnetOpts reso; | |
414 | if(getEDNSSubnetOptsFromString(opt.second, &reso)) { | |
415 | // cerr<<"EDNS Subnet response: "<<reso.source.toString()<<", scope: "<<reso.scope.toString()<<", family = "<<reso.scope.getNetwork().sin4.sin_family<<endl; | |
416 | /* rfc7871 states that 0 "indicate[s] that the answer is suitable for all addresses in FAMILY", | |
417 | so we might want to still pass the information along to be able to differentiate between | |
418 | IPv4 and IPv6. Still I'm pretty sure it doesn't matter in real life, so let's not duplicate | |
419 | entries in our cache. */ | |
420 | if(reso.scope.getBits()) { | |
421 | uint8_t bits = std::min(reso.scope.getBits(), outgoingECSBits); | |
422 | outgoingECSAddr.truncate(bits); | |
423 | srcmask = Netmask(outgoingECSAddr, bits); | |
424 | } | |
425 | } | |
426 | } | |
427 | } | |
428 | } | |
429 | } | |
430 | ||
431 | #ifdef HAVE_PROTOBUF | |
432 | if(outgoingLoggers) { | |
433 | logIncomingResponse(outgoingLoggers, pbMessage, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, srcmask, len, lwr->d_rcode, lwr->d_records, queryTime, exportTypes); | |
434 | } | |
435 | #endif | |
436 | lwr->d_validpacket=true; | |
437 | return 1; | |
438 | } | |
439 | catch(std::exception &mde) { | |
440 | if(::arg().mustDo("log-common-errors")) | |
441 | g_log<<Logger::Notice<<"Unable to parse packet from remote server "<<ip.toString()<<": "<<mde.what()<<endl; | |
442 | lwr->d_rcode = RCode::FormErr; | |
443 | g_stats.serverParseError++; | |
444 | #ifdef HAVE_PROTOBUF | |
445 | if(outgoingLoggers) { | |
446 | logIncomingResponse(outgoingLoggers, pbMessage, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, srcmask, len, lwr->d_rcode, lwr->d_records, queryTime, exportTypes); | |
447 | } | |
448 | #endif | |
449 | lwr->d_validpacket=false; | |
450 | return 1; // success - oddly enough | |
451 | } | |
452 | catch(...) { | |
453 | g_log<<Logger::Notice<<"Unknown error parsing packet from remote server"<<endl; | |
454 | } | |
455 | ||
456 | g_stats.serverParseError++; | |
457 | ||
458 | out: | |
459 | if(!lwr->d_rcode) | |
460 | lwr->d_rcode=RCode::ServFail; | |
461 | ||
462 | return -1; | |
463 | } | |
464 |