]>
Commit | Line | Data |
---|---|---|
12c86877 | 1 | /* |
12471842 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 | |
0ddde5fb RG |
25 | |
26 | #include <sys/types.h> | |
27 | #include <thread> | |
28 | ||
379ab445 | 29 | #include "packetcache.hh" |
b636533b | 30 | #include "utility.hh" |
12c86877 | 31 | #include "dnsproxy.hh" |
5c409fa2 | 32 | #include "pdnsexception.hh" |
12c86877 BH |
33 | #include "dns.hh" |
34 | #include "logger.hh" | |
35 | #include "statbag.hh" | |
d2116c15 | 36 | #include "dns_random.hh" |
c4e084f2 | 37 | #include "stubresolver.hh" |
c4e084f2 | 38 | #include "arguments.hh" |
519f5484 | 39 | #include "threadname.hh" |
50e2abc0 | 40 | #include "ednsoptions.hh" |
41 | #include "ednssubnet.hh" | |
12c86877 BH |
42 | |
43 | extern StatBag S; | |
12c86877 | 44 | |
c0ebe1da PD |
45 | DNSProxy::DNSProxy(const string& remote) : |
46 | d_xor(dns_random_uint16()) | |
12c86877 | 47 | { |
c0ebe1da PD |
48 | d_resanswers = S.getPointer("recursing-answers"); |
49 | d_resquestions = S.getPointer("recursing-questions"); | |
50 | d_udpanswers = S.getPointer("udp-answers"); | |
2b78726c PL |
51 | |
52 | vector<string> addresses; | |
53 | stringtok(addresses, remote, " ,\t"); | |
40c9a111 RG |
54 | d_remote = ComboAddress(addresses[0], 53); |
55 | ||
c0ebe1da PD |
56 | if ((d_sock = socket(d_remote.sin4.sin_family, SOCK_DGRAM, 0)) < 0) { |
57 | throw PDNSException(string("socket: ") + stringerror()); | |
40c9a111 RG |
58 | } |
59 | ||
c0cc6559 | 60 | ComboAddress local; |
c0ebe1da | 61 | if (d_remote.sin4.sin_family == AF_INET) { |
c0cc6559 | 62 | local = ComboAddress("0.0.0.0"); |
40c9a111 RG |
63 | } |
64 | else { | |
c0cc6559 | 65 | local = ComboAddress("::"); |
40c9a111 | 66 | } |
c0ebe1da | 67 | |
b7349b96 PD |
68 | unsigned int attempts = 0; |
69 | for (; attempts < 10; attempts++) { | |
c0ebe1da PD |
70 | local.sin4.sin_port = htons(10000 + dns_random(50000)); |
71 | ||
b7349b96 | 72 | if (::bind(d_sock, (struct sockaddr*)&local, local.getSocklen()) >= 0) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast) |
12c86877 | 73 | break; |
b7349b96 | 74 | } |
12c86877 | 75 | } |
b7349b96 | 76 | if (attempts == 10) { |
3897b9e1 | 77 | closesocket(d_sock); |
c0ebe1da PD |
78 | d_sock = -1; |
79 | throw PDNSException(string("binding dnsproxy socket: ") + stringerror()); | |
12c86877 BH |
80 | } |
81 | ||
b7349b96 | 82 | if (connect(d_sock, (sockaddr*)&d_remote, d_remote.getSocklen()) < 0) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast) |
c0ebe1da | 83 | throw PDNSException("Unable to UDP connect to remote nameserver " + d_remote.toStringWithPort() + ": " + stringerror()); |
40c9a111 | 84 | } |
12c86877 | 85 | |
c0ebe1da PD |
86 | g_log << Logger::Error << "DNS Proxy launched, local port " << ntohs(local.sin4.sin_port) << ", remote " << d_remote.toStringWithPort() << endl; |
87 | } | |
12c86877 BH |
88 | |
89 | void DNSProxy::go() | |
90 | { | |
b7349b96 PD |
91 | std::thread proxythread([this]() { mainloop(); }); |
92 | proxythread.detach(); | |
12c86877 BH |
93 | } |
94 | ||
713010a2 | 95 | //! look up qname target with r->qtype, plonk it in the answer section of 'r' with name aname |
b7349b96 | 96 | bool DNSProxy::completePacket(std::unique_ptr<DNSPacket>& reply, const DNSName& target, const DNSName& aname, const uint8_t scopeMask) |
d59b894d | 97 | { |
50e2abc0 | 98 | string ECSOptionStr; |
99 | ||
b7349b96 | 100 | if (reply->hasEDNSSubnet()) { |
446e9afb | 101 | DLOG(g_log << "dnsproxy::completePacket: Parsed edns source: " << reply->d_eso.source.toString() << ", scope: " << reply->d_eso.scope.toString() << ", family = " << reply->d_eso.scope.getNetwork().sin4.sin_family << endl); |
b7349b96 | 102 | ECSOptionStr = makeEDNSSubnetOptsString(reply->d_eso); |
c0ebe1da | 103 | DLOG(g_log << "from dnsproxy::completePacket: Creating ECS option string " << makeHexDump(ECSOptionStr) << endl); |
50e2abc0 | 104 | } |
105 | ||
b7349b96 | 106 | if (reply->d_tcp) { |
c4e084f2 | 107 | vector<DNSZoneRecord> ips; |
b7349b96 PD |
108 | int ret1 = 0; |
109 | int ret2 = 0; | |
50e2abc0 | 110 | // rip out edns info here, pass it to the stubDoResolve |
b7349b96 PD |
111 | if (reply->qtype == QType::A || reply->qtype == QType::ANY) { |
112 | ret1 = stubDoResolve(target, QType::A, ips, reply->hasEDNSSubnet() ? &reply->d_eso : nullptr); | |
113 | } | |
114 | if (reply->qtype == QType::AAAA || reply->qtype == QType::ANY) { | |
115 | ret2 = stubDoResolve(target, QType::AAAA, ips, reply->hasEDNSSubnet() ? &reply->d_eso : nullptr); | |
116 | } | |
c4e084f2 | 117 | |
c0ebe1da PD |
118 | if (ret1 != RCode::NoError || ret2 != RCode::NoError) { |
119 | g_log << Logger::Error << "Error resolving for " << aname << " ALIAS " << target << " over UDP, original query came in over TCP"; | |
b9c5ccc5 | 120 | if (ret1 != RCode::NoError) { |
c0ebe1da | 121 | g_log << Logger::Error << ", A-record query returned " << RCode::to_s(ret1); |
b9c5ccc5 PL |
122 | } |
123 | if (ret2 != RCode::NoError) { | |
c0ebe1da | 124 | g_log << Logger::Error << ", AAAA-record query returned " << RCode::to_s(ret2); |
b9c5ccc5 | 125 | } |
c0ebe1da | 126 | g_log << Logger::Error << ", returning SERVFAIL" << endl; |
b7349b96 PD |
127 | reply->clearRecords(); |
128 | reply->setRcode(RCode::ServFail); | |
c0ebe1da PD |
129 | } |
130 | else { | |
b7349b96 | 131 | for (auto& ip : ips) { // NOLINT(readability-identifier-length) |
b9c5ccc5 | 132 | ip.dr.d_name = aname; |
b7349b96 | 133 | reply->addRecord(std::move(ip)); |
b9c5ccc5 | 134 | } |
c4e084f2 PD |
135 | } |
136 | ||
b7349b96 | 137 | uint16_t len = htons(reply->getString().length()); |
c4e084f2 | 138 | string buffer((const char*)&len, 2); |
b7349b96 PD |
139 | buffer.append(reply->getString()); |
140 | writen2WithTimeout(reply->getSocket(), buffer.c_str(), buffer.length(), timeval{::arg().asNum("tcp-idle-timeout"), 0}); | |
c4e084f2 PD |
141 | |
142 | return true; | |
143 | } | |
144 | ||
d59b894d | 145 | uint16_t id; |
b7349b96 | 146 | uint16_t qtype = reply->qtype.getCode(); |
d59b894d | 147 | { |
3a5f4c09 RG |
148 | auto conntrack = d_conntrack.lock(); |
149 | id = getID_locked(*conntrack); | |
d59b894d | 150 | |
151 | ConntrackEntry ce; | |
b7349b96 PD |
152 | ce.id = reply->d.id; |
153 | ce.remote = reply->d_remote; | |
154 | ce.outsock = reply->getSocket(); | |
c0ebe1da | 155 | ce.created = time(nullptr); |
b7349b96 | 156 | ce.qtype = reply->qtype.getCode(); |
561434a6 | 157 | ce.qname = target; |
b7349b96 PD |
158 | ce.anyLocal = reply->d_anyLocal; |
159 | ce.complete = std::move(reply); | |
c0ebe1da | 160 | ce.aname = aname; |
0abea1ca | 161 | ce.anameScopeMask = scopeMask; |
c0ebe1da | 162 | (*conntrack)[id] = std::move(ce); |
d59b894d | 163 | } |
164 | ||
165 | vector<uint8_t> packet; | |
c2826d2e | 166 | DNSPacketWriter pw(packet, target, qtype); |
c0ebe1da PD |
167 | pw.getHeader()->rd = true; |
168 | pw.getHeader()->id = id ^ d_xor; | |
50e2abc0 | 169 | // Add EDNS Subnet if the client sent one - issue #5469 |
170 | if (!ECSOptionStr.empty()) { | |
c0ebe1da | 171 | DLOG(g_log << "from dnsproxy::completePacket: adding ECS option string to packet options " << makeHexDump(ECSOptionStr) << endl); |
50e2abc0 | 172 | DNSPacketWriter::optvect_t opts; |
173 | opts.emplace_back(EDNSOptionCode::ECS, ECSOptionStr); | |
174 | pw.addOpt(512, 0, 0, opts); | |
175 | pw.commit(); | |
176 | } | |
d59b894d | 177 | |
b7349b96 | 178 | if (send(d_sock, packet.data(), packet.size(), 0) < 0) { // zoom |
c0ebe1da | 179 | g_log << Logger::Error << "Unable to send a packet to our recursing backend: " << stringerror() << endl; |
d59b894d | 180 | } |
181 | ||
182 | return true; | |
d59b894d | 183 | } |
184 | ||
12c86877 | 185 | /** This finds us an unused or stale ID. Does not actually clean the contents */ |
3a5f4c09 | 186 | int DNSProxy::getID_locked(map_t& conntrack) |
12c86877 | 187 | { |
b7349b96 PD |
188 | map_t::iterator iter; |
189 | for (int n = 0;; ++n) { // NOLINT(readability-identifier-length) | |
190 | iter = conntrack.find(n); | |
191 | if (iter == conntrack.end()) { | |
12c86877 BH |
192 | return n; |
193 | } | |
b7349b96 PD |
194 | if (iter->second.created < time(nullptr) - 60) { |
195 | if (iter->second.created != 0) { | |
196 | g_log << Logger::Warning << "Recursive query for remote " << iter->second.remote.toStringWithPort() << " with internal id " << n << " was not answered by backend within timeout, reusing id" << endl; | |
197 | iter->second.complete.reset(); | |
c0ebe1da | 198 | S.inc("recursion-unanswered"); |
d59b894d | 199 | } |
12c86877 BH |
200 | return n; |
201 | } | |
202 | } | |
203 | } | |
204 | ||
e52fb6a4 | 205 | void DNSProxy::mainloop() |
12c86877 | 206 | { |
519f5484 | 207 | setThreadName("pdns/dnsproxy"); |
12c86877 BH |
208 | try { |
209 | char buffer[1500]; | |
a683e8bd | 210 | ssize_t len; |
12c86877 | 211 | |
65d8e171 KN |
212 | struct msghdr msgh; |
213 | struct iovec iov; | |
7bec330a | 214 | cmsgbuf_aligned cbuf; |
40c9a111 | 215 | ComboAddress fromaddr; |
65d8e171 | 216 | |
c0ebe1da | 217 | for (;;) { |
40c9a111 | 218 | socklen_t fromaddrSize = sizeof(fromaddr); |
b7349b96 | 219 | len = recvfrom(d_sock, &buffer[0], sizeof(buffer), 0, (struct sockaddr*)&fromaddr, &fromaddrSize); // answer from our backend NOLINT(cppcoreguidelines-pro-type-cstyle-cast) |
c0ebe1da | 220 | if (len < (ssize_t)sizeof(dnsheader)) { |
b7349b96 | 221 | if (len < 0) { |
c0ebe1da | 222 | g_log << Logger::Error << "Error receiving packet from recursor backend: " << stringerror() << endl; |
b7349b96 PD |
223 | } |
224 | else if (len == 0) { | |
c0ebe1da | 225 | g_log << Logger::Error << "Error receiving packet from recursor backend, EOF" << endl; |
b7349b96 PD |
226 | } |
227 | else { | |
c0ebe1da | 228 | g_log << Logger::Error << "Short packet from recursor backend, " << len << " bytes" << endl; |
b7349b96 | 229 | } |
c0ebe1da | 230 | |
4957a608 | 231 | continue; |
12c86877 | 232 | } |
40c9a111 | 233 | if (fromaddr != d_remote) { |
c0ebe1da | 234 | g_log << Logger::Error << "Got answer from unexpected host " << fromaddr.toStringWithPort() << " instead of our recursor backend " << d_remote.toStringWithPort() << endl; |
40c9a111 RG |
235 | continue; |
236 | } | |
12c86877 BH |
237 | (*d_resanswers)++; |
238 | (*d_udpanswers)++; | |
b7349b96 PD |
239 | dnsheader dHead{}; |
240 | memcpy(&dHead, &buffer[0], sizeof(dHead)); | |
12c86877 | 241 | { |
3a5f4c09 | 242 | auto conntrack = d_conntrack.lock(); |
c6b1e59f CH |
243 | if (BYTE_ORDER == BIG_ENDIAN) { |
244 | // this is needed because spoof ID down below does not respect the native byteorder | |
245 | dHead.id = (256 * (uint16_t)buffer[1]) + (uint16_t)buffer[0]; | |
246 | } | |
247 | ||
b7349b96 PD |
248 | auto iter = conntrack->find(dHead.id ^ d_xor); |
249 | if (iter == conntrack->end()) { | |
250 | g_log << Logger::Error << "Discarding untracked packet from recursor backend with id " << (dHead.id ^ d_xor) << ". Conntrack table size=" << conntrack->size() << endl; | |
4957a608 BH |
251 | continue; |
252 | } | |
b7349b96 PD |
253 | if (iter->second.created == 0) { |
254 | g_log << Logger::Error << "Received packet from recursor backend with id " << (dHead.id ^ d_xor) << " which is a duplicate" << endl; | |
4957a608 BH |
255 | continue; |
256 | } | |
c0ebe1da | 257 | |
b7349b96 PD |
258 | dHead.id = iter->second.id; |
259 | memcpy(&buffer[0], &dHead, sizeof(dHead)); // commit spoofed id | |
4957a608 | 260 | |
b7349b96 PD |
261 | DNSPacket packet(false); |
262 | packet.parse(&buffer[0], (size_t)len); | |
7de6b0d5 | 263 | |
b7349b96 PD |
264 | if (packet.qtype.getCode() != iter->second.qtype || packet.qdomain != iter->second.qname) { |
265 | g_log << Logger::Error << "Discarding packet from recursor backend with id " << (dHead.id ^ d_xor) << ", qname or qtype mismatch (" << packet.qtype.getCode() << " v " << iter->second.qtype << ", " << packet.qdomain << " v " << iter->second.qname << ")" << endl; | |
7de6b0d5 BH |
266 | continue; |
267 | } | |
f5310162 | 268 | |
65d8e171 KN |
269 | /* Set up iov and msgh structures. */ |
270 | memset(&msgh, 0, sizeof(struct msghdr)); | |
d808896e | 271 | string reply; // needs to be alive at time of sendmsg! |
b7349b96 | 272 | MOADNSParser mdp(false, packet.getString()); |
12788f57 PD |
273 | // update the EDNS options with info from the resolver - issue #5469 |
274 | // note that this relies on the ECS string encoder to use the source network, and only take the prefix length from scope | |
b7349b96 | 275 | iter->second.complete->d_eso.scope = packet.d_eso.scope; |
446e9afb | 276 | DLOG(g_log << "from dnsproxy::mainLoop: updated EDNS options from resolver EDNS source: " << iter->second.complete->d_eso.source.toString() << " EDNS scope: " << iter->second.complete->d_eso.scope.toString() << endl); |
50e2abc0 | 277 | |
a3928acd | 278 | if (mdp.d_header.rcode == RCode::NoError) { |
c0ebe1da PD |
279 | for (const auto& answer : mdp.d_answers) { |
280 | if (answer.first.d_place == DNSResourceRecord::ANSWER || (answer.first.d_place == DNSResourceRecord::AUTHORITY && answer.first.d_type == QType::SOA)) { | |
d808896e | 281 | |
b7349b96 | 282 | if (answer.first.d_type == iter->second.qtype || (iter->second.qtype == QType::ANY && (answer.first.d_type == QType::A || answer.first.d_type == QType::AAAA))) { |
a3928acd | 283 | DNSZoneRecord dzr; |
b7349b96 | 284 | dzr.dr.d_name = iter->second.aname; |
f80ebc05 | 285 | dzr.dr.d_type = answer.first.d_type; |
c0ebe1da PD |
286 | dzr.dr.d_ttl = answer.first.d_ttl; |
287 | dzr.dr.d_place = answer.first.d_place; | |
d06dcda4 | 288 | dzr.dr.setContent(answer.first.getContent()); |
b7349b96 | 289 | iter->second.complete->addRecord(std::move(dzr)); |
d808896e PL |
290 | } |
291 | } | |
292 | } | |
50e2abc0 | 293 | |
b7349b96 | 294 | iter->second.complete->setRcode(mdp.d_header.rcode); |
c0ebe1da PD |
295 | } |
296 | else { | |
b7349b96 PD |
297 | g_log << Logger::Error << "Error resolving for " << iter->second.aname << " ALIAS " << iter->second.qname << " over UDP, " << QType(iter->second.qtype).toString() << "-record query returned " << RCode::to_s(mdp.d_header.rcode) << ", returning SERVFAIL" << endl; |
298 | iter->second.complete->clearRecords(); | |
299 | iter->second.complete->setRcode(RCode::ServFail); | |
d808896e | 300 | } |
b7349b96 | 301 | reply = iter->second.complete->getString(); |
a3928acd PL |
302 | iov.iov_base = (void*)reply.c_str(); |
303 | iov.iov_len = reply.length(); | |
b7349b96 | 304 | iter->second.complete.reset(); |
65d8e171 KN |
305 | msgh.msg_iov = &iov; |
306 | msgh.msg_iovlen = 1; | |
b7349b96 PD |
307 | msgh.msg_name = (struct sockaddr*)&iter->second.remote; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast) |
308 | msgh.msg_namelen = iter->second.remote.getSocklen(); | |
c0ebe1da | 309 | msgh.msg_control = nullptr; |
65d8e171 | 310 | |
b7349b96 PD |
311 | if (iter->second.anyLocal) { |
312 | addCMsgSrcAddr(&msgh, &cbuf, iter->second.anyLocal.get_ptr(), 0); | |
f5310162 | 313 | } |
b7349b96 | 314 | if (sendmsg(iter->second.outsock, &msgh, 0) < 0) { |
a702a96c | 315 | int err = errno; |
b7349b96 | 316 | g_log << Logger::Warning << "dnsproxy.cc: Error sending reply with sendmsg (socket=" << iter->second.outsock << "): " << stringerror(err) << endl; |
a702a96c | 317 | } |
b7349b96 | 318 | iter->second.created = 0; |
12c86877 BH |
319 | } |
320 | } | |
321 | } | |
c0ebe1da PD |
322 | catch (PDNSException& ae) { |
323 | g_log << Logger::Error << "Fatal error in DNS proxy: " << ae.reason << endl; | |
12c86877 | 324 | } |
c0ebe1da PD |
325 | catch (std::exception& e) { |
326 | g_log << Logger::Error << "Communicator thread died because of STL error: " << e.what() << endl; | |
12c86877 | 327 | } |
c0ebe1da | 328 | catch (...) { |
e6a9dde5 | 329 | g_log << Logger::Error << "Caught unknown exception." << endl; |
12c86877 | 330 | } |
c0ebe1da | 331 | g_log << Logger::Error << "Exiting because DNS proxy failed" << endl; |
5bd2ea7b | 332 | _exit(1); |
12c86877 | 333 | } |
732d9faa | 334 | |
c0ebe1da PD |
335 | DNSProxy::~DNSProxy() |
336 | { | |
337 | if (d_sock > -1) { | |
a7b68ae7 RG |
338 | try { |
339 | closesocket(d_sock); | |
340 | } | |
c0ebe1da | 341 | catch (const PDNSException& e) { |
a7b68ae7 RG |
342 | } |
343 | } | |
344 | ||
c0ebe1da | 345 | d_sock = -1; |
732d9faa | 346 | } |