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

PowerDNS / pdns / 16652482455

31 Jul 2025 02:55PM UTC coverage: 62.906% (-2.9%) from 65.842%
16652482455

Pull #15953

github

web-flow
Merge 1fb353750 into 132cad30c
Pull Request #15953: auth: fallback to TCP if UDP queries return with TC

39754 of 92464 branches covered (42.99%)

Branch coverage included in aggregate %.

0 of 227 new or added lines in 3 files covered. (0.0%)

5151 existing lines in 68 files now uncovered.

122816 of 165969 relevant lines covered (74.0%)

3049890.49 hits per line

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

2.75
/modules/pipebackend/pipebackend.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
#include <string>
26
#include <map>
27
#include <unistd.h>
28
#include <stdlib.h>
29
#include <sstream>
30
#include "coprocess.hh"
31

32
#include "pdns/namespaces.hh"
33

34
#include "pdns/dns.hh"
35
#include "pdns/dnsbackend.hh"
36
#include "pdns/dnspacket.hh"
37
#include "pdns/pdnsexception.hh"
38
#include "pdns/logger.hh"
39
#include "pdns/arguments.hh"
40
#include <sys/socket.h>
41
#include <netinet/in.h>
42
#include <arpa/inet.h>
43
#include "pipebackend.hh"
44

45
// The following requirement guarantees UnknownDomainID will get output as "-1"
46
// for compatibility.
47
static_assert(std::is_signed<domainid_t>::value);
48

49
static const char* kBackendId = "[PIPEBackend]";
50

51
CoWrapper::CoWrapper(const string& command, int timeout, int abiVersion)
UNCOV
52
{
×
UNCOV
53
  d_command = command;
×
UNCOV
54
  d_timeout = timeout;
×
UNCOV
55
  d_abiVersion = abiVersion;
×
UNCOV
56
  launch(); // let exceptions fall through - if initial launch fails, we want to die
×
57
  // I think
UNCOV
58
}
×
59

UNCOV
60
CoWrapper::~CoWrapper() = default;
×
61

62
void CoWrapper::launch()
UNCOV
63
{
×
UNCOV
64
  if (d_cp)
×
UNCOV
65
    return;
×
66

UNCOV
67
  if (d_command.empty())
×
68
    throw ArgException("pipe-command is not specified");
×
69

UNCOV
70
  if (isUnixSocket(d_command)) {
×
71
    d_cp = std::make_unique<UnixRemote>(d_command);
×
72
  }
×
UNCOV
73
  else {
×
UNCOV
74
    auto coprocess = std::make_unique<CoProcess>(d_command, d_timeout);
×
UNCOV
75
    coprocess->launch();
×
UNCOV
76
    d_cp = std::move(coprocess);
×
UNCOV
77
  }
×
78

UNCOV
79
  d_cp->send("HELO\t" + std::to_string(d_abiVersion));
×
UNCOV
80
  string banner;
×
UNCOV
81
  d_cp->receive(banner);
×
UNCOV
82
  g_log << Logger::Error << "Backend launched with banner: " << banner << endl;
×
UNCOV
83
}
×
84

85
void CoWrapper::send(const string& line)
UNCOV
86
{
×
UNCOV
87
  launch();
×
UNCOV
88
  try {
×
UNCOV
89
    d_cp->send(line);
×
UNCOV
90
    return;
×
UNCOV
91
  }
×
UNCOV
92
  catch (PDNSException& ae) {
×
93
    d_cp.reset();
×
94
    throw;
×
95
  }
×
UNCOV
96
}
×
97
void CoWrapper::receive(string& line)
UNCOV
98
{
×
UNCOV
99
  launch();
×
UNCOV
100
  try {
×
UNCOV
101
    d_cp->receive(line);
×
UNCOV
102
    return;
×
UNCOV
103
  }
×
UNCOV
104
  catch (PDNSException& ae) {
×
105
    g_log << Logger::Warning << kBackendId << " Unable to receive data from coprocess. " << ae.reason << endl;
×
106
    d_cp.reset();
×
107
    throw;
×
108
  }
×
UNCOV
109
}
×
110

111
PipeBackend::PipeBackend(const string& suffix)
UNCOV
112
{
×
UNCOV
113
  d_disavow = false;
×
UNCOV
114
  signal(SIGCHLD, SIG_IGN);
×
UNCOV
115
  setArgPrefix("pipe" + suffix);
×
UNCOV
116
  try {
×
UNCOV
117
    launch();
×
UNCOV
118
  }
×
UNCOV
119
  catch (const ArgException& A) {
×
120
    g_log << Logger::Error << kBackendId << " Unable to launch, fatal argument error: " << A.reason << endl;
×
121
    throw;
×
122
  }
×
UNCOV
123
  catch (...) {
×
124
    throw;
×
125
  }
×
UNCOV
126
}
×
127

128
void PipeBackend::launch()
UNCOV
129
{
×
UNCOV
130
  if (d_coproc)
×
UNCOV
131
    return;
×
132

UNCOV
133
  try {
×
UNCOV
134
    if (!getArg("regex").empty()) {
×
135
      d_regex = std::make_unique<Regex>(getArg("regex"));
×
136
    }
×
UNCOV
137
    d_regexstr = getArg("regex");
×
UNCOV
138
    d_abiVersion = getArgAsNum("abi-version");
×
UNCOV
139
    d_coproc = std::make_unique<CoWrapper>(getArg("command"), getArgAsNum("timeout"), getArgAsNum("abi-version"));
×
UNCOV
140
  }
×
141

UNCOV
142
  catch (const ArgException& A) {
×
143
    cleanup();
×
144
    throw;
×
145
  }
×
UNCOV
146
}
×
147

148
/*
149
 * Cleans up the co-process wrapper
150
 */
151
void PipeBackend::cleanup()
UNCOV
152
{
×
UNCOV
153
  d_coproc.reset(nullptr);
×
UNCOV
154
  d_regex.reset();
×
UNCOV
155
  d_regexstr = string();
×
UNCOV
156
  d_abiVersion = 0;
×
UNCOV
157
}
×
158

159
void PipeBackend::lookup(const QType& qtype, const DNSName& qname, domainid_t zoneId, DNSPacket* pkt_p)
UNCOV
160
{
×
UNCOV
161
  try {
×
UNCOV
162
    launch();
×
UNCOV
163
    d_disavow = false;
×
UNCOV
164
    if (d_regex && !d_regex->match(qname.toStringRootDot())) {
×
165
      if (::arg().mustDo("query-logging"))
×
166
        g_log << Logger::Error << "Query for '" << qname << "' failed regex '" << d_regexstr << "'" << endl;
×
167
      d_disavow = true; // don't pass to backend
×
168
    }
×
UNCOV
169
    else {
×
UNCOV
170
      ostringstream query;
×
UNCOV
171
      string localIP = "0.0.0.0";
×
UNCOV
172
      string remoteIP = "0.0.0.0";
×
UNCOV
173
      Netmask realRemote("0.0.0.0/0");
×
UNCOV
174
      if (pkt_p) {
×
UNCOV
175
        localIP = pkt_p->getLocal().toString();
×
UNCOV
176
        realRemote = pkt_p->getRealRemote();
×
UNCOV
177
        remoteIP = pkt_p->getInnerRemote().toString();
×
UNCOV
178
      }
×
179
      // abi-version = 1
180
      // type    qname           qclass  qtype   id      remote-ip-address
UNCOV
181
      query << "Q\t" << qname.toStringRootDot() << "\tIN\t" << qtype.toString() << "\t" << zoneId << "\t" << remoteIP;
×
182

183
      // add the local-ip-address if abi-version is set to 2
UNCOV
184
      if (d_abiVersion >= 2)
×
UNCOV
185
        query << "\t" << localIP;
×
UNCOV
186
      if (d_abiVersion >= 3)
×
UNCOV
187
        query << "\t" << realRemote.toString();
×
188

UNCOV
189
      if (::arg().mustDo("query-logging"))
×
190
        g_log << Logger::Error << "Query: '" << query.str() << "'" << endl;
×
UNCOV
191
      d_coproc->send(query.str());
×
UNCOV
192
    }
×
UNCOV
193
  }
×
UNCOV
194
  catch (PDNSException& pe) {
×
195
    g_log << Logger::Error << kBackendId << " Error from coprocess: " << pe.reason << endl;
×
196
    d_disavow = true;
×
197
  }
×
UNCOV
198
  d_qtype = qtype;
×
UNCOV
199
  d_qname = qname;
×
UNCOV
200
}
×
201

202
bool PipeBackend::list(const ZoneName& target, domainid_t domain_id, bool /* include_disabled */)
203
{
×
204
  try {
×
205
    launch();
×
206
    d_disavow = false;
×
207
    ostringstream query;
×
208
    // The question format:
209

210
    // type    qname           qclass  qtype   id      ip-address
211
    if (d_abiVersion >= 4)
×
212
      query << "AXFR\t" << domain_id << "\t" << target.toStringRootDot();
×
213
    else
×
214
      query << "AXFR\t" << domain_id;
×
215

216
    d_coproc->send(query.str());
×
217
  }
×
218
  catch (PDNSException& ae) {
×
219
    g_log << Logger::Error << kBackendId << " Error from coprocess: " << ae.reason << endl;
×
220
  }
×
221
  d_qname = DNSName(std::to_string(domain_id)); // why do we store a number here??
×
222
  return true;
×
223
}
×
224

225
string PipeBackend::directBackendCmd(const string& query)
226
{
×
227
  if (d_abiVersion < 5)
×
228
    return "not supported on ABI version " + std::to_string(d_abiVersion) + " (use ABI version 5 or later)\n";
×
229

230
  try {
×
231
    launch();
×
232
    ostringstream oss;
×
233
    oss << "CMD\t" << query;
×
234
    d_coproc->send(oss.str());
×
235
  }
×
236
  catch (PDNSException& ae) {
×
237
    g_log << Logger::Error << kBackendId << " Error from coprocess: " << ae.reason << endl;
×
238
    cleanup();
×
239
  }
×
240

241
  ostringstream oss;
×
242
  while (true) {
×
243
    string line;
×
244
    d_coproc->receive(line);
×
245
    if (line == "END")
×
246
      break;
×
247
    oss << line << std::endl;
×
248
  };
×
249

250
  return oss.str();
×
251
}
×
252

253
//! For the dynamic loader
254
DNSBackend* PipeBackend::maker()
255
{
×
256
  try {
×
257
    return new PipeBackend();
×
258
  }
×
259
  catch (...) {
×
260
    g_log << Logger::Error << kBackendId << " Unable to instantiate a pipebackend!" << endl;
×
261
    return nullptr;
×
262
  }
×
263
}
×
264

265
PipeBackend::~PipeBackend()
UNCOV
266
{
×
UNCOV
267
  cleanup();
×
UNCOV
268
}
×
269

270
bool PipeBackend::get(DNSResourceRecord& r)
UNCOV
271
{
×
UNCOV
272
  if (d_disavow) // this query has been blocked
×
273
    return false;
×
274

UNCOV
275
  string line;
×
276

277
  // The answer format:
278
  // DATA    qname           qclass  qtype   ttl     id      content
UNCOV
279
  unsigned int extraFields = 0;
×
UNCOV
280
  if (d_abiVersion >= 3)
×
UNCOV
281
    extraFields = 2;
×
282

UNCOV
283
  try {
×
UNCOV
284
    launch();
×
UNCOV
285
    for (;;) {
×
UNCOV
286
      d_coproc->receive(line);
×
UNCOV
287
      vector<string> parts;
×
UNCOV
288
      stringtok(parts, line, "\t");
×
UNCOV
289
      if (parts.empty()) {
×
290
        g_log << Logger::Error << kBackendId << " Coprocess returned empty line in query for " << d_qname << endl;
×
291
        throw PDNSException("Format error communicating with coprocess");
×
292
      }
×
UNCOV
293
      else if (parts[0] == "FAIL") {
×
294
        throw DBException("coprocess returned a FAIL");
×
295
      }
×
UNCOV
296
      else if (parts[0] == "END") {
×
UNCOV
297
        return false;
×
UNCOV
298
      }
×
UNCOV
299
      else if (parts[0] == "LOG") {
×
300
        g_log << Logger::Error << "Coprocess: " << line.substr(4) << endl;
×
301
        continue;
×
302
      }
×
UNCOV
303
      else if (parts[0] == "DATA") { // yay
×
UNCOV
304
        if (parts.size() < 7 + extraFields) {
×
305
          g_log << Logger::Error << kBackendId << " Coprocess returned incomplete or empty line in data section for query for " << d_qname << endl;
×
306
          throw PDNSException("Format error communicating with coprocess in data section");
×
307
          // now what?
308
        }
×
309

UNCOV
310
        if (d_abiVersion >= 3) {
×
UNCOV
311
          r.scopeMask = std::stoi(parts[1]);
×
UNCOV
312
          r.auth = (parts[2] == "1");
×
UNCOV
313
        }
×
UNCOV
314
        else {
×
UNCOV
315
          r.scopeMask = 0;
×
UNCOV
316
          r.auth = true;
×
UNCOV
317
        }
×
UNCOV
318
        r.qname = DNSName(parts[1 + extraFields]);
×
UNCOV
319
        r.qtype = parts[3 + extraFields];
×
UNCOV
320
        pdns::checked_stoi_into(r.ttl, parts[4 + extraFields]);
×
UNCOV
321
        pdns::checked_stoi_into(r.domain_id, parts[5 + extraFields]);
×
322

UNCOV
323
        if (r.qtype.getCode() != QType::MX && r.qtype.getCode() != QType::SRV) {
×
UNCOV
324
          r.content.clear();
×
UNCOV
325
          for (unsigned int n = 6 + extraFields; n < parts.size(); ++n) {
×
UNCOV
326
            if (n != 6 + extraFields)
×
327
              r.content.append(1, ' ');
×
UNCOV
328
            r.content.append(parts[n]);
×
UNCOV
329
          }
×
UNCOV
330
        }
×
UNCOV
331
        else {
×
UNCOV
332
          if (parts.size() < 8 + extraFields) {
×
333
            g_log << Logger::Error << kBackendId << " Coprocess returned incomplete MX/SRV line in data section for query for " << d_qname << endl;
×
334
            throw PDNSException("Format error communicating with coprocess in data section of MX/SRV record");
×
335
          }
×
336

UNCOV
337
          r.content = parts[6 + extraFields] + " " + parts[7 + extraFields];
×
UNCOV
338
        }
×
UNCOV
339
        break;
×
UNCOV
340
      }
×
UNCOV
341
      else
×
UNCOV
342
        throw PDNSException("Coprocess backend sent incorrect response '" + line + "'");
×
UNCOV
343
    }
×
UNCOV
344
  }
×
UNCOV
345
  catch (DBException& dbe) {
×
346
    g_log << Logger::Error << kBackendId << " " << dbe.reason << endl;
×
347
    throw;
×
348
  }
×
UNCOV
349
  catch (PDNSException& pe) {
×
350
    g_log << Logger::Error << kBackendId << " " << pe.reason << endl;
×
351
    cleanup();
×
352
    throw;
×
353
  }
×
UNCOV
354
  return true;
×
UNCOV
355
}
×
356

357
//
358
// Magic class that is activated when the dynamic library is loaded
359
//
360

361
class PipeFactory : public BackendFactory
362
{
363
public:
364
  PipeFactory() :
365
    BackendFactory("pipe") {}
626✔
366

367
  void declareArguments(const string& suffix = "") override
UNCOV
368
  {
×
UNCOV
369
    declare(suffix, "command", "Command to execute for piping questions to", "");
×
UNCOV
370
    declare(suffix, "timeout", "Number of milliseconds to wait for an answer", "2000");
×
UNCOV
371
    declare(suffix, "regex", "Regular expression of queries to pass to coprocess", "");
×
UNCOV
372
    declare(suffix, "abi-version", "Version of the pipe backend ABI", "1");
×
UNCOV
373
  }
×
374

375
  DNSBackend* make(const string& suffix = "") override
UNCOV
376
  {
×
UNCOV
377
    return new PipeBackend(suffix);
×
UNCOV
378
  }
×
379
};
380

381
class PipeLoader
382
{
383
public:
384
  PipeLoader()
385
  {
626✔
386
    BackendMakers().report(std::make_unique<PipeFactory>());
626✔
387
    g_log << Logger::Info << kBackendId << " This is the pipe backend version " VERSION
626✔
388
#ifndef REPRODUCIBLE
626✔
389
          << " (" __DATE__ " " __TIME__ ")"
626✔
390
#endif
626✔
391
          << " reporting" << endl;
626✔
392
  }
626✔
393
};
394

395
static PipeLoader pipeloader;
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