• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 14703970107

28 Apr 2025 08:50AM UTC coverage: 63.599% (+0.005%) from 63.594%
14703970107

push

github

web-flow
Merge pull request #15438 from rgacogne/ddist-fix-quic-freebsd-2

dnsdist: Only pass source addresses on sockets bound to ANY

41961 of 100700 branches covered (41.67%)

Branch coverage included in aggregate %.

16 of 19 new or added lines in 4 files covered. (84.21%)

52 existing lines in 12 files now uncovered.

129449 of 168816 relevant lines covered (76.68%)

4631284.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.94
/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
#include <boost/uuid/uuid_io.hpp>
44

45
extern StatBag S;
46

47
DNSProxy::DNSProxy(const string& remote, const string& udpPortRange) :
48
  d_xor(dns_random_uint16())
49
{
71✔
50
  d_resanswers = S.getPointer("recursing-answers");
71✔
51
  d_resquestions = S.getPointer("recursing-questions");
71✔
52
  d_udpanswers = S.getPointer("udp-answers");
71✔
53

54
  vector<string> addresses;
71✔
55
  stringtok(addresses, remote, " ,\t");
71✔
56
  d_remote = ComboAddress(addresses[0], 53);
71✔
57

58
  vector<string> parts;
71✔
59
  stringtok(parts, udpPortRange, " ");
71✔
60
  if (parts.size() != 2) {
71!
61
    throw PDNSException("DNS Proxy UDP port range must contain exactly one lower and one upper bound");
×
62
  }
×
63
  unsigned long portRangeLow = std::stoul(parts.at(0));
71✔
64
  unsigned long portRangeHigh = std::stoul(parts.at(1));
71✔
65
  if (portRangeLow < 1 || portRangeHigh > 65535) {
71!
66
    throw PDNSException("DNS Proxy UDP port range values out of valid port bounds (1 to 65535)");
×
67
  }
×
68
  if (portRangeLow >= portRangeHigh) {
71!
69
    throw PDNSException("DNS Proxy UDP port range upper bound " + std::to_string(portRangeHigh) + " must be higher than lower bound (" + std::to_string(portRangeLow) + ")");
×
70
  }
×
71

72
  if ((d_sock = socket(d_remote.sin4.sin_family, SOCK_DGRAM, 0)) < 0) {
71!
73
    throw PDNSException(string("socket: ") + stringerror());
×
74
  }
×
75

76
  ComboAddress local;
71✔
77
  if (d_remote.sin4.sin_family == AF_INET) {
71!
78
    local = ComboAddress("0.0.0.0");
71✔
79
  }
71✔
80
  else {
×
81
    local = ComboAddress("::");
×
82
  }
×
83

84
  unsigned int attempts = 0;
71✔
85
  for (; attempts < 10; attempts++) {
71!
86
    local.sin4.sin_port = htons(portRangeLow + dns_random(portRangeHigh - portRangeLow));
71✔
87

88
    if (::bind(d_sock, (struct sockaddr*)&local, local.getSocklen()) >= 0) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
71!
89
      break;
71✔
90
    }
71✔
91
  }
71✔
92
  if (attempts == 10) {
71!
93
    closesocket(d_sock);
×
94
    d_sock = -1;
×
95
    throw PDNSException(string("binding dnsproxy socket: ") + stringerror());
×
96
  }
×
97

98
  if (connect(d_sock, (sockaddr*)&d_remote, d_remote.getSocklen()) < 0) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
71!
99
    throw PDNSException("Unable to UDP connect to remote nameserver " + d_remote.toStringWithPort() + ": " + stringerror());
×
100
  }
×
101

102
  g_log << Logger::Error << "DNS Proxy launched, local port " << ntohs(local.sin4.sin_port) << ", remote " << d_remote.toStringWithPort() << endl;
71✔
103
}
71✔
104

105
void DNSProxy::go()
106
{
71✔
107
  std::thread proxythread([this]() { mainloop(); });
71✔
108
  proxythread.detach();
71✔
109
}
71✔
110

111
//! look up qname 'target' with reply->qtype, plonk it in the answer section of 'reply' with name 'aname'
112
bool DNSProxy::completePacket(std::unique_ptr<DNSPacket>& reply, const DNSName& target, const DNSName& aname, const uint8_t scopeMask)
113
{
301✔
114
  string ECSOptionStr;
301✔
115

116
  if (reply->hasEDNSSubnet()) {
301!
117
    DLOG(g_log << "dnsproxy::completePacket: Parsed edns source: " << reply->d_eso.getSource().toString() << ", scope: " << Netmask(reply->d_eso.getSource().getNetwork(), reply->d_eso.getScopePrefixLength()).toString() << ", family = " << std::to_string(reply->d_eso.getFamily()) << endl);
×
118
    ECSOptionStr = reply->d_eso.makeOptString();
×
119
    DLOG(g_log << "from dnsproxy::completePacket: Creating ECS option string " << makeHexDump(ECSOptionStr) << endl);
×
120
  }
×
121

122
  if (reply->d_tcp) {
301✔
123
    vector<DNSZoneRecord> ips;
114✔
124
    int ret1 = 0;
114✔
125
    int ret2 = 0;
114✔
126
    // rip out edns info here, pass it to the stubDoResolve
127
    if (reply->qtype == QType::A || reply->qtype == QType::ANY) {
114!
128
      ret1 = stubDoResolve(target, QType::A, ips, reply->hasEDNSSubnet() ? &reply->d_eso : nullptr);
57!
129
    }
57✔
130
    if (reply->qtype == QType::AAAA || reply->qtype == QType::ANY) {
114!
131
      ret2 = stubDoResolve(target, QType::AAAA, ips, reply->hasEDNSSubnet() ? &reply->d_eso : nullptr);
57!
132
    }
57✔
133

134
    if (ret1 != RCode::NoError || ret2 != RCode::NoError) {
114!
UNCOV
135
      g_log << Logger::Error << "Error resolving for " << aname << " ALIAS " << target << " over UDP, original query came in over TCP";
×
UNCOV
136
      if (ret1 != RCode::NoError) {
×
UNCOV
137
        g_log << Logger::Error << ", A-record query returned " << RCode::to_s(ret1);
×
UNCOV
138
      }
×
UNCOV
139
      if (ret2 != RCode::NoError) {
×
140
        g_log << Logger::Error << ", AAAA-record query returned " << RCode::to_s(ret2);
×
141
      }
×
UNCOV
142
      g_log << Logger::Error << ", returning SERVFAIL" << endl;
×
UNCOV
143
      reply->clearRecords();
×
UNCOV
144
      reply->setRcode(RCode::ServFail);
×
UNCOV
145
    }
×
146
    else {
114✔
147
      for (auto& ip : ips) { // NOLINT(readability-identifier-length)
114✔
148
        ip.dr.d_name = aname;
114✔
149
        reply->addRecord(std::move(ip));
114✔
150
      }
114✔
151
    }
114✔
152

153
    uint16_t len = htons(reply->getString().length());
114✔
154
    string buffer((const char*)&len, 2);
114✔
155
    buffer.append(reply->getString());
114✔
156
    writen2WithTimeout(reply->getSocket(), buffer.c_str(), buffer.length(), timeval{::arg().asNum("tcp-idle-timeout"), 0});
114✔
157

158
    return true;
114✔
159
  }
114✔
160

161
  uint16_t id;
187✔
162
  uint16_t qtype = reply->qtype.getCode();
187✔
163
  {
187✔
164
    auto conntrack = d_conntrack.lock();
187✔
165
    id = getID_locked(*conntrack);
187✔
166

167
    ConntrackEntry ce;
187✔
168
    ce.id = reply->d.id;
187✔
169
    ce.remote = reply->d_remote;
187✔
170
    ce.outsock = reply->getSocket();
187✔
171
    ce.created = time(nullptr);
187✔
172
    ce.qtype = reply->qtype.getCode();
187✔
173
    ce.qname = target;
187✔
174
    ce.anyLocal = reply->d_anyLocal;
187✔
175
    ce.complete = std::move(reply);
187✔
176
    ce.aname = aname;
187✔
177
    ce.anameScopeMask = scopeMask;
187✔
178
    (*conntrack)[id] = std::move(ce);
187✔
179
  }
187✔
180

181
  vector<uint8_t> packet;
187✔
182
  DNSPacketWriter pw(packet, target, qtype);
187✔
183
  pw.getHeader()->rd = true;
187✔
184
  pw.getHeader()->id = id ^ d_xor;
187✔
185
  // Add EDNS Subnet if the client sent one - issue #5469
186
  if (!ECSOptionStr.empty()) {
187!
187
    DLOG(g_log << "from dnsproxy::completePacket: adding ECS option string to packet options " << makeHexDump(ECSOptionStr) << endl);
×
188
    DNSPacketWriter::optvect_t opts;
×
189
    opts.emplace_back(EDNSOptionCode::ECS, ECSOptionStr);
×
190
    pw.addOpt(512, 0, 0, opts);
×
191
    pw.commit();
×
192
  }
×
193

194
  if (send(d_sock, packet.data(), packet.size(), 0) < 0) { // zoom
187!
195
    g_log << Logger::Error << "Unable to send a packet to our recursing backend: " << stringerror() << endl;
×
196
  }
×
197

198
  return true;
187✔
199
}
301✔
200

201
/** This finds us an unused or stale ID. Does not actually clean the contents */
202
int DNSProxy::getID_locked(map_t& conntrack)
203
{
187✔
204
  map_t::iterator iter;
187✔
205
  for (int n = 0;; ++n) { // NOLINT(readability-identifier-length)
187✔
206
    iter = conntrack.find(n);
187✔
207
    if (iter == conntrack.end()) {
187✔
208
      return n;
37✔
209
    }
37✔
210
    if (iter->second.created < time(nullptr) - 60) {
150!
211
      if (iter->second.created != 0) {
150!
212
        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;
×
213
        iter->second.complete.reset();
×
214
        S.inc("recursion-unanswered");
×
215
      }
×
216
      return n;
150✔
217
    }
150✔
218
  }
150✔
219
}
187✔
220

221
void DNSProxy::mainloop()
222
{
71✔
223
  setThreadName("pdns/dnsproxy");
71✔
224
  try {
71✔
225
    char buffer[1500];
71✔
226
    ssize_t len;
71✔
227

228
    struct msghdr msgh;
71✔
229
    struct iovec iov;
71✔
230
    cmsgbuf_aligned cbuf;
71✔
231
    ComboAddress fromaddr;
71✔
232

233
    for (;;) {
258✔
234
      socklen_t fromaddrSize = sizeof(fromaddr);
258✔
235
      len = recvfrom(d_sock, &buffer[0], sizeof(buffer), 0, (struct sockaddr*)&fromaddr, &fromaddrSize); // answer from our backend  NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
258✔
236
      if (len < (ssize_t)sizeof(dnsheader)) {
258!
237
        if (len < 0) {
×
238
          g_log << Logger::Error << "Error receiving packet from recursor backend: " << stringerror() << endl;
×
239
        }
×
240
        else if (len == 0) {
×
241
          g_log << Logger::Error << "Error receiving packet from recursor backend, EOF" << endl;
×
242
        }
×
243
        else {
×
244
          g_log << Logger::Error << "Short packet from recursor backend, " << len << " bytes" << endl;
×
245
        }
×
246

247
        continue;
×
248
      }
×
249
      if (fromaddr != d_remote) {
258!
250
        g_log << Logger::Error << "Got answer from unexpected host " << fromaddr.toStringWithPort() << " instead of our recursor backend " << d_remote.toStringWithPort() << endl;
×
251
        continue;
×
252
      }
×
253
      (*d_resanswers)++;
258✔
254
      (*d_udpanswers)++;
258✔
255
      dnsheader dHead{};
258✔
256
      memcpy(&dHead, &buffer[0], sizeof(dHead));
258✔
257
      {
258✔
258
        auto conntrack = d_conntrack.lock();
258✔
259
        if (BYTE_ORDER == BIG_ENDIAN) {
258✔
260
          // this is needed because spoof ID down below does not respect the native byteorder
261
          dHead.id = (256 * (uint16_t)buffer[1]) + (uint16_t)buffer[0];
×
262
        }
×
263

264
        auto iter = conntrack->find(dHead.id ^ d_xor);
258✔
265
        if (iter == conntrack->end()) {
258!
266
          g_log << Logger::Error << "Discarding untracked packet from recursor backend with id " << (dHead.id ^ d_xor) << ". Conntrack table size=" << conntrack->size() << endl;
×
267
          continue;
×
268
        }
×
269
        if (iter->second.created == 0) {
258!
270
          g_log << Logger::Error << "Received packet from recursor backend with id " << (dHead.id ^ d_xor) << " which is a duplicate" << endl;
×
271
          continue;
×
272
        }
×
273

274
        dHead.id = iter->second.id;
258✔
275
        memcpy(&buffer[0], &dHead, sizeof(dHead)); // commit spoofed id
258✔
276

277
        DNSPacket packet(false);
258✔
278
        packet.parse(&buffer[0], (size_t)len);
258✔
279

280
        if (packet.qtype.getCode() != iter->second.qtype || packet.qdomain != iter->second.qname) {
258!
281
          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;
×
282
          continue;
×
283
        }
×
284

285
        /* Set up iov and msgh structures. */
286
        memset(&msgh, 0, sizeof(struct msghdr));
258✔
287
        string reply; // needs to be alive at time of sendmsg!
258✔
288
        MOADNSParser mdp(false, packet.getString());
258✔
289
        // update the EDNS options with info from the resolver - issue #5469
290
        // note that this relies on the ECS string encoder to use the source network, and only take the prefix length from scope
291
        iter->second.complete->d_eso.setScopePrefixLength(packet.d_eso.getScopePrefixLength());
258✔
292
        DLOG(g_log << "from dnsproxy::mainLoop: updated EDNS options from resolver EDNS source: " << iter->second.complete->d_eso.getSource().toString() << " EDNS scope: " << iter->second.complete->d_eso.getScope().toString() << endl);
258✔
293

294
        if (mdp.d_header.rcode == RCode::NoError) {
258✔
295
          for (const auto& answer : mdp.d_answers) {
187✔
296
            if (answer.d_place == DNSResourceRecord::ANSWER || (answer.d_place == DNSResourceRecord::AUTHORITY && answer.d_type == QType::SOA)) {
187!
297

298
              if (answer.d_type == iter->second.qtype || (iter->second.qtype == QType::ANY && (answer.d_type == QType::A || answer.d_type == QType::AAAA))) {
187!
299
                DNSZoneRecord dzr;
187✔
300
                dzr.dr.d_name = iter->second.aname;
187✔
301
                dzr.dr.d_type = answer.d_type;
187✔
302
                dzr.dr.d_ttl = answer.d_ttl;
187✔
303
                dzr.dr.d_place = answer.d_place;
187✔
304
                dzr.dr.setContent(answer.getContent());
187✔
305
                iter->second.complete->addRecord(std::move(dzr));
187✔
306
              }
187✔
307
            }
187✔
308
          }
187✔
309

310
          iter->second.complete->setRcode(mdp.d_header.rcode);
187✔
311
        }
187✔
312
        else {
71✔
313
          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;
71✔
314
          iter->second.complete->clearRecords();
71✔
315
          iter->second.complete->setRcode(RCode::ServFail);
71✔
316
        }
71✔
317
        reply = iter->second.complete->getString();
258✔
318
        iov.iov_base = (void*)reply.c_str();
258✔
319
        iov.iov_len = reply.length();
258✔
320
        iter->second.complete.reset();
258✔
321
        msgh.msg_iov = &iov;
258✔
322
        msgh.msg_iovlen = 1;
258✔
323
        msgh.msg_name = (struct sockaddr*)&iter->second.remote; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
258✔
324
        msgh.msg_namelen = iter->second.remote.getSocklen();
258✔
325
        msgh.msg_control = nullptr;
258✔
326

327
        if (iter->second.anyLocal) {
258!
328
          addCMsgSrcAddr(&msgh, &cbuf, iter->second.anyLocal.get_ptr(), 0);
×
329
        }
×
330
        if (sendmsg(iter->second.outsock, &msgh, 0) < 0) {
258!
331
          int err = errno;
×
332
          g_log << Logger::Warning << "dnsproxy.cc: Error sending reply with sendmsg (socket=" << iter->second.outsock << "): " << stringerror(err) << endl;
×
333
        }
×
334
        iter->second.created = 0;
258✔
335
      }
258✔
336
    }
258✔
337
  }
71✔
338
  catch (PDNSException& ae) {
71✔
339
    g_log << Logger::Error << "Fatal error in DNS proxy: " << ae.reason << endl;
×
340
  }
×
341
  catch (std::exception& e) {
71✔
342
    g_log << Logger::Error << "Communicator thread died because of STL error: " << e.what() << endl;
×
343
  }
×
344
  catch (...) {
71✔
345
    g_log << Logger::Error << "Caught unknown exception." << endl;
×
346
  }
×
347
  g_log << Logger::Error << "Exiting because DNS proxy failed" << endl;
71✔
348
  _exit(1);
×
349
}
71✔
350

351
DNSProxy::~DNSProxy()
352
{
×
353
  if (d_sock > -1) {
×
354
    try {
×
355
      closesocket(d_sock);
×
356
    }
×
357
    catch (const PDNSException& e) {
×
358
    }
×
359
  }
×
360

361
  d_sock = -1;
×
362
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc