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

PowerDNS / pdns / 19202600185

09 Nov 2025 03:18AM UTC coverage: 73.052% (+0.06%) from 72.995%
19202600185

Pull #16459

github

web-flow
Merge 423666510 into f324028a2
Pull Request #16459: Fix try/except/as notation

38311 of 63074 branches covered (60.74%)

Branch coverage included in aggregate %.

127423 of 163798 relevant lines covered (77.79%)

12342679.43 hits per line

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

60.79
/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)
52
{
20✔
53
  d_command = command;
20✔
54
  d_timeout = timeout;
20✔
55
  d_abiVersion = abiVersion;
20✔
56
  launch(); // let exceptions fall through - if initial launch fails, we want to die
20✔
57
  // I think
58
}
20✔
59

60
CoWrapper::~CoWrapper() = default;
4✔
61

62
void CoWrapper::launch()
63
{
108✔
64
  if (d_cp)
108✔
65
    return;
88✔
66

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

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

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

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

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

127
void PipeBackend::launch()
128
{
108✔
129
  if (d_coproc)
108✔
130
    return;
87✔
131

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

141
  catch (const ArgException& A) {
21✔
142
    cleanup();
×
143
    throw;
×
144
  }
×
145
}
21✔
146

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

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

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

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

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

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

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

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

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

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

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

252
PipeBackend::~PipeBackend()
253
{
4✔
254
  cleanup();
4✔
255
}
4✔
256

257
void PipeBackend::throwTooShortDataError(const std::string& what)
258
{
×
259
  g_log << Logger::Error << kBackendId << " Coprocess returned incomplete or empty line in data section for query for " << d_qname << endl;
×
260
  throw PDNSException("Format error communicating with coprocess in data section" + what);
×
261
}
×
262

263
bool PipeBackend::get(DNSResourceRecord& r)
264
{
54✔
265
  if (d_disavow) // this query has been blocked
54!
266
    return false;
×
267

268
  string line;
54✔
269

270
  // The answer format:
271
  // DATA    qname           qclass  qtype   ttl     id      content
272

273
  try {
54✔
274
    launch();
54✔
275
    for (;;) {
54✔
276
      d_coproc->receive(line);
54✔
277
      vector<string> parts;
54✔
278
      stringtok(parts, line, "\t");
54✔
279
      if (parts.empty()) {
54!
280
        g_log << Logger::Error << kBackendId << " Coprocess returned empty line in query for " << d_qname << endl;
×
281
        throw PDNSException("Format error communicating with coprocess");
×
282
      }
×
283
      else if (parts[0] == "FAIL") {
54!
284
        throw DBException("coprocess returned a FAIL");
×
285
      }
×
286
      else if (parts[0] == "END") {
54✔
287
        return false;
34✔
288
      }
34✔
289
      else if (parts[0] == "LOG") {
20!
290
        g_log << Logger::Error << "Coprocess: " << line.substr(4) << endl;
×
291
        continue;
×
292
      }
×
293
      else if (parts[0] == "DATA") { // yay
20!
294
        // The shortest records (ENT) require 6 fields. Other may require more
295
        // and will have a stricter check once the record type has been
296
        // computed.
297
        if (parts.size() < 6 + (d_abiVersion >= 3 ? 2 : 0)) {
20!
298
          throwTooShortDataError("");
×
299
        }
×
300

301
        if (d_abiVersion >= 3) {
20✔
302
          r.scopeMask = std::stoi(parts[1]);
16✔
303
          r.auth = (parts[2] == "1");
16✔
304
          parts.erase(parts.begin() + 1, parts.begin() + 3);
16✔
305
        }
16✔
306
        else {
4✔
307
          r.scopeMask = 0;
4✔
308
          r.auth = true;
4✔
309
        }
4✔
310
        r.qname = DNSName(parts[1]);
20✔
311
        r.qtype = parts[3];
20✔
312
        pdns::checked_stoi_into(r.ttl, parts[4]);
20✔
313
        pdns::checked_stoi_into(r.domain_id, parts[5]);
20✔
314

315
        switch (r.qtype.getCode()) {
20✔
316
        case QType::ENT:
×
317
          // No other data to process
318
          r.content.clear();
×
319
          break;
×
320
        case QType::MX:
2✔
321
        case QType::SRV:
2!
322
          if (parts.size() < 8) {
2!
323
            throwTooShortDataError(" of MX/SRV record");
×
324
          }
×
325
          r.content = parts[6] + " " + parts[7];
2✔
326
          break;
2✔
327
        default:
18✔
328
          if (parts.size() < 7) {
18!
329
            throwTooShortDataError("");
×
330
          }
×
331
          r.content = parts[6];
18✔
332
          for (std::vector<std::string>::size_type pos = 7; pos < parts.size(); ++pos) {
18!
333
            r.content.append(1, ' ');
×
334
            r.content.append(parts[pos]);
×
335
          }
×
336
          break;
18✔
337
        }
20✔
338
        break;
20✔
339
      }
20✔
340
      else
×
341
        throw PDNSException("Coprocess backend sent incorrect response '" + line + "'");
×
342
    }
54✔
343
  }
54✔
344
  catch (DBException& dbe) {
54✔
345
    g_log << Logger::Error << kBackendId << " " << dbe.reason << endl;
×
346
    throw;
×
347
  }
×
348
  catch (PDNSException& pe) {
54✔
349
    g_log << Logger::Error << kBackendId << " " << pe.reason << endl;
×
350
    cleanup();
×
351
    throw;
×
352
  }
×
353
  return true;
20✔
354
}
54✔
355

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

360
class PipeFactory : public BackendFactory
361
{
362
public:
363
  PipeFactory() :
364
    BackendFactory("pipe") {}
11,738✔
365

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

374
  DNSBackend* make(const string& suffix = "") override
375
  {
20✔
376
    return new PipeBackend(suffix);
20✔
377
  }
20✔
378
};
379

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

394
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