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

PowerDNS / pdns / 13196902717

07 Feb 2025 09:23AM UTC coverage: 64.106% (-0.6%) from 64.745%
13196902717

push

github

web-flow
Merge pull request #15126 from rgacogne/ddist-ignore-re2-broken-cflags

dnsdist: Ignore `re2`'s broken compilation flags

37895 of 90356 branches covered (41.94%)

Branch coverage included in aggregate %.

127151 of 167100 relevant lines covered (76.09%)

5174909.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
static const char* kBackendId = "[PIPEBackend]";
46

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

56
CoWrapper::~CoWrapper() = default;
×
57

58
void CoWrapper::launch()
59
{
×
60
  if (d_cp)
×
61
    return;
×
62

63
  if (d_command.empty())
×
64
    throw ArgException("pipe-command is not specified");
×
65

66
  if (isUnixSocket(d_command)) {
×
67
    d_cp = std::make_unique<UnixRemote>(d_command);
×
68
  }
×
69
  else {
×
70
    auto coprocess = std::make_unique<CoProcess>(d_command, d_timeout);
×
71
    coprocess->launch();
×
72
    d_cp = std::move(coprocess);
×
73
  }
×
74

75
  d_cp->send("HELO\t" + std::to_string(d_abiVersion));
×
76
  string banner;
×
77
  d_cp->receive(banner);
×
78
  g_log << Logger::Error << "Backend launched with banner: " << banner << endl;
×
79
}
×
80

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

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

124
void PipeBackend::launch()
125
{
×
126
  if (d_coproc)
×
127
    return;
×
128

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

138
  catch (const ArgException& A) {
×
139
    cleanup();
×
140
    throw;
×
141
  }
×
142
}
×
143

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

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

179
      // add the local-ip-address if abi-version is set to 2
180
      if (d_abiVersion >= 2)
×
181
        query << "\t" << localIP;
×
182
      if (d_abiVersion >= 3)
×
183
        query << "\t" << realRemote.toString();
×
184

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

198
bool PipeBackend::list(const DNSName& target, int inZoneId, bool /* include_disabled */)
199
{
×
200
  try {
×
201
    launch();
×
202
    d_disavow = false;
×
203
    ostringstream query;
×
204
    // The question format:
205

206
    // type    qname           qclass  qtype   id      ip-address
207
    if (d_abiVersion >= 4)
×
208
      query << "AXFR\t" << inZoneId << "\t" << target.toStringRootDot();
×
209
    else
×
210
      query << "AXFR\t" << inZoneId;
×
211

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

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

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

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

246
  return oss.str();
×
247
}
×
248

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

261
PipeBackend::~PipeBackend()
262
{
×
263
  cleanup();
×
264
}
×
265

266
bool PipeBackend::get(DNSResourceRecord& r)
267
{
×
268
  if (d_disavow) // this query has been blocked
×
269
    return false;
×
270

271
  string line;
×
272

273
  // The answer format:
274
  // DATA    qname           qclass  qtype   ttl     id      content
275
  unsigned int extraFields = 0;
×
276
  if (d_abiVersion >= 3)
×
277
    extraFields = 2;
×
278

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

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

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

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

353
//
354
// Magic class that is activated when the dynamic library is loaded
355
//
356

357
class PipeFactory : public BackendFactory
358
{
359
public:
360
  PipeFactory() :
361
    BackendFactory("pipe") {}
3,092✔
362

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

371
  DNSBackend* make(const string& suffix = "") override
372
  {
×
373
    return new PipeBackend(suffix);
×
374
  }
×
375
};
376

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

391
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