]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/dnsproxy.cc
Merge pull request #14021 from Habbie/auth-lua-join-whitespace
[thirdparty/pdns.git] / pdns / dnsproxy.cc
CommitLineData
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
43extern StatBag S;
12c86877 44
c0ebe1da
PD
45DNSProxy::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
89void 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 96bool 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 186int 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 205void 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
335DNSProxy::~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}