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