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

PowerDNS / pdns / 22225698541

20 Feb 2026 01:20PM UTC coverage: 70.913% (-0.7%) from 71.654%
22225698541

Pull #16693

github

web-flow
Merge 12ab81d89 into da25f7afd
Pull Request #16693: auth: plumbing for structured logging

45342 of 79966 branches covered (56.7%)

Branch coverage included in aggregate %.

892 of 2611 new or added lines in 70 files covered. (34.16%)

390 existing lines in 48 files now uncovered.

130608 of 168156 relevant lines covered (77.67%)

7371735.34 hits per line

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

34.93
/modules/remotebackend/remotebackend.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
#include <limits>
23
#ifdef HAVE_CONFIG_H
24
#include "config.h"
25
#endif
26
#include "remotebackend.hh"
27

28
// The following requirement guarantees UnknownDomainID will get output as "-1"
29
// in the Json data for compatibility.
30
static_assert(std::is_signed<domainid_t>::value);
31

32
// The following requirement is a consequency of JsonInt not able to store
33
// anything larger than "int".
34
static_assert(sizeof(domainid_t) <= sizeof(int));
35

36
static const char* kBackendId = "[RemoteBackend]";
37

38
/**
39
 * Forwarder for value. This is just in case
40
 * we need to do some treatment to the value before
41
 * sending it downwards.
42
 */
43
bool Connector::send(Json& value)
44
{
640✔
45
  return send_message(value) > 0;
640✔
46
}
640✔
47

48
/**
49
 * Helper for handling receiving of data.
50
 * Basically what happens here is that we check
51
 * that the receiving happened ok, and extract
52
 * result. Logging is performed here, too.
53
 */
54
bool Connector::recv(Json& value)
55
{
640✔
56
  if (recv_message(value) > 0) {
640!
57
    bool retval = true;
640✔
58
    if (value["result"] == Json()) {
640!
59
      throw PDNSException("No 'result' field in response from remote process");
×
60
    }
×
61
    if (value["result"].is_bool() && !boolFromJson(value, "result", false)) {
640✔
62
      retval = false;
24✔
63
    }
24✔
64
    for (const auto& message : value["log"].array_items()) {
640✔
65
      SLOG(g_log << Logger::Info << "[remotebackend]: " << message.string_value() << std::endl,
103!
66
           d_slog->info(Logr::Info, "received", "message", Logging::Loggable(message.string_value())));
103✔
67
    }
103✔
68
    return retval;
640✔
69
  }
640✔
70
  throw PDNSException("Unknown error while receiving data");
×
71
}
640✔
72

73
/**
74
 * Standard ctor and dtor
75
 */
76
RemoteBackend::RemoteBackend(const std::string& suffix)
77
{
92✔
78
  setArgPrefix("remote" + suffix);
92✔
79

80
  this->d_connstr = getArg("connection-string");
92✔
81
  this->d_dnssec = mustDo("dnssec");
92✔
82

83
  build();
92✔
84
}
92✔
85

86
RemoteBackend::~RemoteBackend() = default;
74✔
87

88
bool RemoteBackend::send(Json& value)
89
{
581✔
90
  try {
581✔
91
    if (!connector->send(value)) {
581!
92
      // XXX does this work even though we throw?
93
      this->connector.reset();
×
94
      build();
×
95
      throw DBException("Could not send a message to remote process");
×
96
    }
×
97
  }
581✔
98
  catch (const PDNSException& ex) {
581✔
99
    throw DBException("Exception caught when sending: " + ex.reason);
×
100
  }
×
101
  return true;
581✔
102
}
581✔
103

104
bool RemoteBackend::recv(Json& value)
105
{
581✔
106
  try {
581✔
107
    return connector->recv(value);
581✔
108
  }
581✔
109
  catch (const PDNSException& ex) {
581✔
110
    this->connector.reset();
×
111
    build();
×
112
    throw DBException("Exception caught when receiving: " + ex.reason);
×
113
  }
×
114
  catch (const std::exception& e) {
581✔
115
    this->connector.reset();
×
116
    build();
×
117
    throw DBException("Exception caught when receiving: " + std::string(e.what()));
×
118
  }
×
119
}
581✔
120

121
/**
122
 * Builds connector based on options
123
 * Currently supports unix,pipe and http
124
 */
125
int RemoteBackend::build()
126
{
92✔
127
  std::vector<std::string> parts;
92✔
128
  std::string type;
92✔
129
  std::string opts;
92✔
130
  std::map<std::string, std::string> options;
92✔
131

132
  // connstr is of format "type:options"
133
  size_t pos = 0;
92✔
134
  pos = d_connstr.find_first_of(':');
92✔
135
  if (pos == std::string::npos) {
92!
136
    throw PDNSException("Invalid connection string: malformed");
×
137
  }
×
138

139
  type = d_connstr.substr(0, pos);
92✔
140
  opts = d_connstr.substr(pos + 1);
92✔
141

142
  // tokenize the string on comma
143
  stringtok(parts, opts, ",");
92✔
144

145
  // find out some options and parse them while we're at it
146
  for (const auto& opt : parts) {
140✔
147
    std::string key;
140✔
148
    std::string val;
140✔
149
    // make sure there is something else than air in the option...
150
    if (opt.find_first_not_of(" ") == std::string::npos) {
140!
151
      continue;
×
152
    }
×
153

154
    // split it on '='. if not found, we treat it as "yes"
155
    pos = opt.find_first_of("=");
140✔
156

157
    if (pos == std::string::npos) {
140!
158
      key = opt;
×
159
      val = "yes";
×
160
    }
×
161
    else {
140✔
162
      key = opt.substr(0, pos);
140✔
163
      val = opt.substr(pos + 1);
140✔
164
    }
140✔
165
    options[key] = std::move(val);
140✔
166
  }
140✔
167

168
  // connectors know what they are doing
169
  if (type == "unix") {
92✔
170
    this->connector = std::make_unique<UnixsocketConnector>(d_slog, options);
23✔
171
  }
23✔
172
  else if (type == "http") {
69✔
173
    this->connector = std::make_unique<HTTPConnector>(d_slog, options);
23✔
174
  }
23✔
175
  else if (type == "zeromq") {
46✔
176
#ifdef REMOTEBACKEND_ZEROMQ
23✔
177
    this->connector = std::make_unique<ZeroMQConnector>(d_slog, options);
23✔
178
#else
179
    throw PDNSException("Invalid connection string: zeromq connector support not enabled. Recompile with --enable-remotebackend-zeromq");
180
#endif
181
  }
23✔
182
  else if (type == "pipe") {
23!
183
    this->connector = std::make_unique<PipeConnector>(d_slog, options);
23✔
184
  }
23✔
185
  else {
×
186
    throw PDNSException("Invalid connection string: unknown connector");
×
187
  }
×
188

189
  return -1;
92✔
190
}
92✔
191

192
/**
193
 * The functions here are just remote json stubs that send and receive the method call
194
 * data is mainly left alone, some defaults are assumed.
195
 */
196
void RemoteBackend::lookup(const QType& qtype, const DNSName& qdomain, domainid_t zoneId, DNSPacket* pkt_p)
197
{
148✔
198
  if (d_index != -1) {
148!
199
    throw PDNSException("Attempt to lookup while one running");
×
200
  }
×
201

202
  string localIP = "0.0.0.0";
148✔
203
  string remoteIP = "0.0.0.0";
148✔
204
  string realRemote = "0.0.0.0/0";
148✔
205

206
  if (pkt_p != nullptr) {
148✔
207
    localIP = pkt_p->getLocal().toString();
56✔
208
    realRemote = pkt_p->getRealRemote().toString();
56✔
209
    remoteIP = pkt_p->getInnerRemote().toString();
56✔
210
  }
56✔
211

212
  Json query = Json::object{
148✔
213
    {"method", "lookup"},
148✔
214
    {"parameters", Json::object{{"qtype", qtype.toString()}, {"qname", qdomain.toString()}, {"remote", remoteIP}, {"local", localIP}, {"real-remote", realRemote}, {"zone-id", zoneId}}}};
148✔
215

216
  if (!this->send(query) || !this->recv(d_result)) {
148!
217
    return;
×
218
  }
×
219

220
  // OK. we have result parameters in result. do not process empty result.
221
  if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
148!
222
    return;
60✔
223
  }
60✔
224

225
  d_index = 0;
88✔
226
}
88✔
227

228
// Similar to lookup above, but passes an extra include_disabled parameter.
229
void RemoteBackend::APILookup(const QType& qtype, const DNSName& qdomain, domainid_t zoneId, bool include_disabled)
230
{
×
231
  if (d_index != -1) {
×
232
    throw PDNSException("Attempt to lookup while one running");
×
233
  }
×
234

235
  string localIP = "0.0.0.0";
×
236
  string remoteIP = "0.0.0.0";
×
237
  string realRemote = "0.0.0.0/0";
×
238

239
  Json query = Json::object{
×
240
    {"method", "APILookup"},
×
241
    {"parameters", Json::object{{"qtype", qtype.toString()}, {"qname", qdomain.toString()}, {"remote", remoteIP}, {"local", localIP}, {"real-remote", realRemote}, {"zone-id", zoneId}, {"include-disabled", include_disabled}}}};
×
242

243
  if (!this->send(query) || !this->recv(d_result)) {
×
244
    return;
×
245
  }
×
246

247
  // OK. we have result parameters in result. do not process empty result.
248
  if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
×
249
    return;
×
250
  }
×
251

252
  d_index = 0;
×
253
}
×
254

255
bool RemoteBackend::list(const ZoneName& target, domainid_t domain_id, bool include_disabled)
256
{
16✔
257
  if (d_index != -1) {
16!
258
    throw PDNSException("Attempt to lookup while one running");
×
259
  }
×
260

261
  Json query = Json::object{
16✔
262
    {"method", "list"},
16✔
263
    {"parameters", Json::object{{"zonename", target.toString()}, {"domain_id", domain_id}, {"include_disabled", include_disabled}}}};
16✔
264

265
  if (!this->send(query) || !this->recv(d_result)) {
16!
266
    return false;
×
267
  }
×
268
  if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
16!
269
    return false;
×
270
  }
×
271

272
  d_index = 0;
16✔
273
  return true;
16✔
274
}
16✔
275

276
bool RemoteBackend::get(DNSResourceRecord& rr)
277
{
478✔
278
  if (d_index == -1) {
478✔
279
    return false;
164✔
280
  }
164✔
281

282
  rr.qtype = stringFromJson(d_result["result"][d_index], "qtype");
314✔
283
  rr.qname = DNSName(stringFromJson(d_result["result"][d_index], "qname"));
314✔
284
  rr.qclass = QClass::IN;
314✔
285
  rr.content = stringFromJson(d_result["result"][d_index], "content");
314✔
286
  rr.ttl = d_result["result"][d_index]["ttl"].int_value();
314✔
287
  rr.domain_id = static_cast<domainid_t>(intFromJson(d_result["result"][d_index], "domain_id", UnknownDomainID));
314✔
288
  if (d_dnssec) {
314✔
289
    rr.auth = (intFromJson(d_result["result"][d_index], "auth", 1) != 0);
216✔
290
  }
216✔
291
  else {
98✔
292
    rr.auth = true;
98✔
293
  }
98✔
294
  rr.scopeMask = d_result["result"][d_index]["scopeMask"].int_value();
314✔
295
  d_index++;
314✔
296

297
  // id index is out of bounds, we know the results end here.
298
  if (d_index == static_cast<int>(d_result["result"].array_items().size())) {
314✔
299
    d_result = Json();
104✔
300
    d_index = -1;
104✔
301
  }
104✔
302
  return true;
314✔
303
}
478✔
304

305
// NOLINTNEXTLINE(readability-identifier-length)
306
bool RemoteBackend::getBeforeAndAfterNamesAbsolute(domainid_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
307
{
261✔
308
  // no point doing dnssec if it's not supported
309
  if (!d_dnssec) {
261!
310
    return false;
×
311
  }
×
312

313
  Json query = Json::object{
261✔
314
    {"method", "getBeforeAndAfterNamesAbsolute"},
261✔
315
    {"parameters", Json::object{{"id", Json(id)}, {"qname", qname.toString()}}}};
261✔
316
  Json answer;
261✔
317

318
  if (!this->send(query) || !this->recv(answer)) {
261!
319
    return false;
×
320
  }
×
321

322
  unhashed = DNSName(stringFromJson(answer["result"], "unhashed"));
261✔
323
  before.clear();
261✔
324
  after.clear();
261✔
325
  if (answer["result"]["before"] != Json()) {
261!
326
    before = DNSName(stringFromJson(answer["result"], "before"));
261✔
327
  }
261✔
328
  if (answer["result"]["after"] != Json()) {
261!
329
    after = DNSName(stringFromJson(answer["result"], "after"));
261✔
330
  }
261✔
331

332
  return true;
261✔
333
}
261✔
334

335
bool RemoteBackend::getAllDomainMetadata(const ZoneName& name, std::map<std::string, std::vector<std::string>>& meta)
336
{
48✔
337
  Json query = Json::object{
48✔
338
    {"method", "getAllDomainMetadata"},
48✔
339
    {"parameters", Json::object{{"name", name.toString()}}}};
48✔
340

341
  if (!this->send(query)) {
48!
342
    return false;
×
343
  }
×
344

345
  meta.clear();
48✔
346

347
  Json answer;
48✔
348
  // not mandatory to implement
349
  if (!this->recv(answer)) {
48!
350
    return true;
×
351
  }
×
352

353
  for (const auto& pair : answer["result"].object_items()) {
48!
354
    if (pair.second.is_array()) {
×
355
      for (const auto& val : pair.second.array_items()) {
×
356
        meta[pair.first].push_back(asString(val));
×
357
      }
×
358
    }
×
359
    else {
×
360
      meta[pair.first].push_back(asString(pair.second));
×
361
    }
×
362
  }
×
363

364
  return true;
48✔
365
}
48✔
366

367
bool RemoteBackend::getDomainMetadata(const ZoneName& name, const std::string& kind, std::vector<std::string>& meta)
368
{
16✔
369
  Json query = Json::object{
16✔
370
    {"method", "getDomainMetadata"},
16✔
371
    {"parameters", Json::object{{"name", name.toString()}, {"kind", kind}}}};
16✔
372

373
  if (!this->send(query)) {
16!
374
    return false;
×
375
  }
×
376

377
  meta.clear();
16✔
378

379
  Json answer;
16✔
380
  // not mandatory to implement
381
  if (!this->recv(answer)) {
16!
382
    return true;
×
383
  }
×
384

385
  if (answer["result"].is_array()) {
16!
386
    for (const auto& row : answer["result"].array_items()) {
16!
387
      meta.push_back(row.string_value());
×
388
    }
×
389
  }
16✔
390
  else if (answer["result"].is_string()) {
×
391
    meta.push_back(answer["result"].string_value());
×
392
  }
×
393

394
  return true;
16✔
395
}
16✔
396

397
bool RemoteBackend::setDomainMetadata(const ZoneName& name, const std::string& kind, const std::vector<std::string>& meta)
398
{
×
399
  Json query = Json::object{
×
400
    {"method", "setDomainMetadata"},
×
401
    {"parameters", Json::object{{"name", name.toString()}, {"kind", kind}, {"value", meta}}}};
×
402

403
  Json answer;
×
404
  if (!this->send(query) || !this->recv(answer)) {
×
405
    return false;
×
406
  }
×
407

408
  return boolFromJson(answer, "result", false);
×
409
}
×
410

411
bool RemoteBackend::getDomainKeys(const ZoneName& name, std::vector<DNSBackend::KeyData>& keys)
412
{
48✔
413
  // no point doing dnssec if it's not supported
414
  if (!d_dnssec) {
48✔
415
    return false;
12✔
416
  }
12✔
417

418
  Json query = Json::object{
36✔
419
    {"method", "getDomainKeys"},
36✔
420
    {"parameters", Json::object{{"name", name.toString()}}}};
36✔
421

422
  Json answer;
36✔
423
  if (!this->send(query) || !this->recv(answer)) {
36!
424
    return false;
8✔
425
  }
8✔
426

427
  keys.clear();
28✔
428

429
  for (const auto& jsonKey : answer["result"].array_items()) {
28✔
430
    DNSBackend::KeyData key;
28✔
431
    key.id = intFromJson(jsonKey, "id");
28✔
432
    key.flags = intFromJson(jsonKey, "flags");
28✔
433
    key.active = asBool(jsonKey["active"]);
28✔
434
    key.published = boolFromJson(jsonKey, "published", true);
28✔
435
    key.content = stringFromJson(jsonKey, "content");
28✔
436
    keys.emplace_back(std::move(key));
28✔
437
  }
28✔
438

439
  return true;
28✔
440
}
36✔
441

442
bool RemoteBackend::removeDomainKey(const ZoneName& name, unsigned int keyId)
443
{
×
444
  // no point doing dnssec if it's not supported
445
  if (!d_dnssec) {
×
446
    return false;
×
447
  }
×
448

449
  Json query = Json::object{
×
450
    {"method", "removeDomainKey"},
×
451
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(keyId)}}}};
×
452

453
  Json answer;
×
454
  return this->send(query) && this->recv(answer);
×
455
}
×
456

457
bool RemoteBackend::addDomainKey(const ZoneName& name, const KeyData& key, int64_t& keyId)
458
{
8✔
459
  // no point doing dnssec if it's not supported
460
  if (!d_dnssec) {
8!
461
    return false;
×
462
  }
×
463

464
  Json query = Json::object{
8✔
465
    {"method", "addDomainKey"},
8✔
466
    {"parameters", Json::object{{"name", name.toString()}, {"key", Json::object{{"flags", static_cast<int>(key.flags)}, {"active", key.active}, {"published", key.published}, {"content", key.content}}}}}};
8✔
467

468
  Json answer;
8✔
469
  if (!this->send(query) || !this->recv(answer)) {
8!
470
    return false;
×
471
  }
×
472

473
  keyId = answer["result"].int_value();
8✔
474
  return keyId >= 0;
8✔
475
}
8✔
476

477
bool RemoteBackend::activateDomainKey(const ZoneName& name, unsigned int keyId)
478
{
×
479
  // no point doing dnssec if it's not supported
480
  if (!d_dnssec) {
×
481
    return false;
×
482
  }
×
483

484
  Json query = Json::object{
×
485
    {"method", "activateDomainKey"},
×
486
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(keyId)}}}};
×
487

488
  Json answer;
×
489
  return this->send(query) && this->recv(answer);
×
490
}
×
491

492
bool RemoteBackend::deactivateDomainKey(const ZoneName& name, unsigned int keyId)
493
{
×
494
  // no point doing dnssec if it's not supported
495
  if (!d_dnssec) {
×
496
    return false;
×
497
  }
×
498

499
  Json query = Json::object{
×
500
    {"method", "deactivateDomainKey"},
×
501
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(keyId)}}}};
×
502

503
  Json answer;
×
504
  return this->send(query) && this->recv(answer);
×
505
}
×
506

507
bool RemoteBackend::publishDomainKey(const ZoneName& name, unsigned int keyId)
508
{
×
509
  // no point doing dnssec if it's not supported
510
  if (!d_dnssec) {
×
511
    return false;
×
512
  }
×
513

514
  Json query = Json::object{
×
515
    {"method", "publishDomainKey"},
×
516
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(keyId)}}}};
×
517

518
  Json answer;
×
519
  return this->send(query) && this->recv(answer);
×
520
}
×
521

522
bool RemoteBackend::unpublishDomainKey(const ZoneName& name, unsigned int keyId)
523
{
×
524
  // no point doing dnssec if it's not supported
525
  if (!d_dnssec) {
×
526
    return false;
×
527
  }
×
528

529
  Json query = Json::object{
×
530
    {"method", "unpublishDomainKey"},
×
531
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(keyId)}}}};
×
532

533
  Json answer;
×
534
  return this->send(query) && this->recv(answer);
×
535
}
×
536

537
unsigned int RemoteBackend::getCapabilities()
538
{
233✔
539
  unsigned int caps = CAP_DIRECT | CAP_LIST | CAP_SEARCH;
233✔
540
  if (d_dnssec) {
233✔
541
    caps |= CAP_DNSSEC;
229✔
542
  }
229✔
543
  return caps;
233✔
544
}
233✔
545

546
bool RemoteBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, std::string& content)
547
{
×
548
  // no point doing dnssec if it's not supported
549
  if (!d_dnssec) {
×
550
    return false;
×
551
  }
×
552

553
  Json query = Json::object{
×
554
    {"method", "getTSIGKey"},
×
555
    {"parameters", Json::object{{"name", name.toString()}}}};
×
556

557
  Json answer;
×
558
  if (!this->send(query) || !this->recv(answer)) {
×
559
    return false;
×
560
  }
×
561

562
  algorithm = DNSName(stringFromJson(answer["result"], "algorithm"));
×
563
  content = stringFromJson(answer["result"], "content");
×
564

565
  return true;
×
566
}
×
567

568
bool RemoteBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const std::string& content)
569
{
×
570
  // no point doing dnssec if it's not supported
571
  if (!d_dnssec) {
×
572
    return false;
×
573
  }
×
574

575
  Json query = Json::object{
×
576
    {"method", "setTSIGKey"},
×
577
    {"parameters", Json::object{{"name", name.toString()}, {"algorithm", algorithm.toString()}, {"content", content}}}};
×
578

579
  Json answer;
×
580
  return connector->send(query) && connector->recv(answer);
×
581
}
×
582

583
bool RemoteBackend::deleteTSIGKey(const DNSName& name)
584
{
×
585
  // no point doing dnssec if it's not supported
586
  if (!d_dnssec) {
×
587
    return false;
×
588
  }
×
589
  Json query = Json::object{
×
590
    {"method", "deleteTSIGKey"},
×
591
    {"parameters", Json::object{{"name", name.toString()}}}};
×
592

593
  Json answer;
×
594
  return connector->send(query) && connector->recv(answer);
×
595
}
×
596

597
bool RemoteBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
598
{
×
599
  // no point doing dnssec if it's not supported
600
  if (!d_dnssec) {
×
601
    return false;
×
602
  }
×
603
  Json query = Json::object{
×
604
    {"method", "getTSIGKeys"},
×
605
    {"parameters", Json::object{}}};
×
606

607
  Json answer;
×
608
  if (!connector->send(query) || !connector->recv(answer)) {
×
609
    return false;
×
610
  }
×
611

612
  for (const auto& jsonKey : answer["result"].array_items()) {
×
613
    struct TSIGKey key;
×
614
    key.name = DNSName(stringFromJson(jsonKey, "name"));
×
615
    key.algorithm = DNSName(stringFromJson(jsonKey, "algorithm"));
×
616
    key.key = stringFromJson(jsonKey, "content");
×
617
    keys.push_back(key);
×
618
  }
×
619

620
  return true;
×
621
}
×
622

623
void RemoteBackend::parseDomainInfo(const Json& obj, DomainInfo& di)
624
{
24✔
625
  di.id = static_cast<domainid_t>(intFromJson(obj, "id", UnknownDomainID));
24✔
626
  di.zone = ZoneName(stringFromJson(obj, "zone"));
24✔
627
  for (const auto& primary : obj["masters"].array_items()) {
24!
628
    di.primaries.emplace_back(primary.string_value(), 53);
×
629
  }
×
630

631
  di.notified_serial = static_cast<unsigned int>(doubleFromJson(obj, "notified_serial", 0));
24✔
632
  di.serial = static_cast<unsigned int>(obj["serial"].number_value());
24✔
633
  di.last_check = static_cast<time_t>(obj["last_check"].number_value());
24✔
634

635
  string kind;
24✔
636
  if (obj["kind"].is_string()) {
24!
637
    kind = stringFromJson(obj, "kind");
24✔
638
  }
24✔
639
  if (kind == "master") {
24!
640
    di.kind = DomainInfo::Primary;
×
641
  }
×
642
  else if (kind == "slave") {
24!
643
    di.kind = DomainInfo::Secondary;
×
644
  }
×
645
  else {
24✔
646
    di.kind = DomainInfo::Native;
24✔
647
  }
24✔
648
  di.backend = this;
24✔
649
}
24✔
650

651
bool RemoteBackend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool /* getSerial */)
652
{
24✔
653
  if (domain.empty()) {
24!
654
    return false;
×
655
  }
×
656

657
  Json query = Json::object{
24✔
658
    {"method", "getDomainInfo"},
24✔
659
    {"parameters", Json::object{{"name", domain.toString()}}}};
24✔
660

661
  Json answer;
24✔
662
  if (!this->send(query) || !this->recv(answer)) {
24!
663
    return false;
×
664
  }
×
665

666
  this->parseDomainInfo(answer["result"], info);
24✔
667
  return true;
24✔
668
}
24✔
669

670
// NOLINTNEXTLINE(readability-identifier-length)
671
void RemoteBackend::setNotified(domainid_t id, uint32_t serial)
672
{
×
673
  Json query = Json::object{
×
674
    {"method", "setNotified"},
×
675
    {"parameters", Json::object{{"id", id}, {"serial", static_cast<double>(serial)}}}};
×
676

677
  Json answer;
×
678
  if (!this->send(query) || !this->recv(answer)) {
×
NEW
679
    SLOG(g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setNotified(" << id << "," << serial << ")" << endl,
×
NEW
680
         d_slog->info(Logr::Error, "Failed to execute RPC for RemoteBackend::setNotified", "domain_id", Logging::Loggable(id), "serial", Logging::Loggable(serial)));
×
681
  }
×
682
}
×
683

684
bool RemoteBackend::autoPrimaryBackend(const string& ipAddress, const ZoneName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** ddb)
685
{
×
686
  Json::array rrset;
×
687

688
  for (const auto& ns : nsset) {
×
689
    rrset.push_back(Json::object{
×
690
      {"qtype", ns.qtype.toString()},
×
691
      {"qname", ns.qname.toString()},
×
692
      {"qclass", QClass::IN.getCode()},
×
693
      {"content", ns.content},
×
694
      {"ttl", static_cast<int>(ns.ttl)},
×
695
      {"auth", ns.auth}});
×
696
  }
×
697

698
  Json query = Json::object{
×
699
    {"method", "superMasterBackend"},
×
700
    {"parameters", Json::object{{"ip", ipAddress}, {"domain", domain.toString()}, {"nsset", rrset}}}};
×
701

702
  *ddb = nullptr;
×
703

704
  Json answer;
×
705
  if (!this->send(query) || !this->recv(answer)) {
×
706
    return false;
×
707
  }
×
708

709
  // we are the backend
710
  *ddb = this;
×
711

712
  // we allow simple true as well...
713
  if (answer["result"].is_object()) {
×
714
    *account = stringFromJson(answer["result"], "account");
×
715
    *nameserver = stringFromJson(answer["result"], "nameserver");
×
716
  }
×
717

718
  return true;
×
719
}
×
720

721
bool RemoteBackend::createSecondaryDomain(const string& ipAddress, const ZoneName& domain, const string& nameserver, const string& account)
722
{
×
723
  Json query = Json::object{
×
724
    {"method", "createSlaveDomain"},
×
725
    {"parameters", Json::object{
×
726
                     {"ip", ipAddress},
×
727
                     {"domain", domain.toString()},
×
728
                     {"nameserver", nameserver},
×
729
                     {"account", account},
×
730
                   }}};
×
731

732
  Json answer;
×
733
  return this->send(query) && this->recv(answer);
×
734
}
×
735

736
bool RemoteBackend::replaceRRSet(domainid_t domain_id, const DNSName& qname, const QType& qtype, const vector<DNSResourceRecord>& rrset)
737
{
×
738
  Json::array json_rrset;
×
739
  for (const auto& rr : rrset) {
×
740
    json_rrset.push_back(Json::object{
×
741
      {"qtype", rr.qtype.toString()},
×
742
      {"qname", rr.qname.toString()},
×
743
      {"qclass", QClass::IN.getCode()},
×
744
      {"content", rr.content},
×
745
      {"ttl", static_cast<int>(rr.ttl)},
×
746
      {"auth", rr.auth}});
×
747
  }
×
748

749
  Json query = Json::object{
×
750
    {"method", "replaceRRSet"},
×
751
    {"parameters", Json::object{{"domain_id", domain_id}, {"qname", qname.toString()}, {"qtype", qtype.toString()}, {"trxid", static_cast<double>(d_trxid)}, {"rrset", json_rrset}}}};
×
752

753
  Json answer;
×
754
  return this->send(query) && this->recv(answer);
×
755
}
×
756

757
bool RemoteBackend::feedRecord(const DNSResourceRecord& rr, const DNSName& ordername, bool /* ordernameIsNSEC3 */)
758
{
×
759
  Json query = Json::object{
×
760
    {"method", "feedRecord"},
×
761
    {"parameters", Json::object{
×
762
                     {"rr", Json::object{{"qtype", rr.qtype.toString()}, {"qname", rr.qname.toString()}, {"qclass", QClass::IN.getCode()}, {"content", rr.content}, {"ttl", static_cast<int>(rr.ttl)}, {"auth", rr.auth}, {"ordername", (ordername.empty() ? Json() : ordername.toString())}}},
×
763
                     {"trxid", static_cast<double>(d_trxid)},
×
764
                   }}};
×
765

766
  Json answer;
×
767
  return this->send(query) && this->recv(answer); // XXX FIXME this API should not return 'true' I think -ahu
×
768
}
×
769

770
bool RemoteBackend::feedEnts(domainid_t domain_id, map<DNSName, bool>& nonterm)
771
{
×
772
  Json::array nts;
×
773

774
  for (const auto& t : nonterm) {
×
775
    nts.push_back(Json::object{
×
776
      {"nonterm", t.first.toString()},
×
777
      {"auth", t.second}});
×
778
  }
×
779

780
  Json query = Json::object{
×
781
    {"method", "feedEnts"},
×
782
    {"parameters", Json::object{{"domain_id", domain_id}, {"trxid", static_cast<double>(d_trxid)}, {"nonterm", nts}}},
×
783
  };
×
784

785
  Json answer;
×
786
  return this->send(query) && this->recv(answer);
×
787
}
×
788

789
bool RemoteBackend::feedEnts3(domainid_t domain_id, const DNSName& domain, map<DNSName, bool>& nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow)
790
{
×
791
  Json::array nts;
×
792

793
  for (const auto& t : nonterm) {
×
794
    nts.push_back(Json::object{
×
795
      {"nonterm", t.first.toString()},
×
796
      {"auth", t.second}});
×
797
  }
×
798

799
  Json query = Json::object{
×
800
    {"method", "feedEnts3"},
×
801
    {"parameters", Json::object{{"domain_id", domain_id}, {"domain", domain.toString()}, {"times", ns3prc.d_iterations}, {"salt", ns3prc.d_salt}, {"narrow", narrow}, {"trxid", static_cast<double>(d_trxid)}, {"nonterm", nts}}},
×
802
  };
×
803

804
  Json answer;
×
805
  return this->send(query) && this->recv(answer);
×
806
}
×
807

808
bool RemoteBackend::startTransaction(const ZoneName& domain, domainid_t domain_id)
809
{
16✔
810
  this->d_trxid = time((time_t*)nullptr);
16✔
811

812
  Json query = Json::object{
16✔
813
    {"method", "startTransaction"},
16✔
814
    {"parameters", Json::object{{"domain", domain.toString()}, {"domain_id", domain_id}, {"trxid", static_cast<double>(d_trxid)}}}};
16✔
815

816
  Json answer;
16✔
817
  if (!this->send(query) || !this->recv(answer)) {
16!
818
    d_trxid = -1;
16✔
819
    return false;
16✔
820
  }
16✔
821
  return true;
×
822
}
16✔
823

824
bool RemoteBackend::commitTransaction()
825
{
16✔
826
  if (d_trxid == -1) {
16!
827
    return false;
16✔
828
  }
16✔
829

830
  Json query = Json::object{
×
831
    {"method", "commitTransaction"},
×
832
    {"parameters", Json::object{{"trxid", static_cast<double>(d_trxid)}}}};
×
833

834
  d_trxid = -1;
×
835
  Json answer;
×
836
  return this->send(query) && this->recv(answer);
×
837
}
16✔
838

839
bool RemoteBackend::abortTransaction()
840
{
×
841
  if (d_trxid == -1) {
×
842
    return false;
×
843
  }
×
844

845
  Json query = Json::object{
×
846
    {"method", "abortTransaction"},
×
847
    {"parameters", Json::object{{"trxid", static_cast<double>(d_trxid)}}}};
×
848

849
  d_trxid = -1;
×
850
  Json answer;
×
851
  return this->send(query) && this->recv(answer);
×
852
}
×
853

854
string RemoteBackend::directBackendCmd(const string& querystr)
855
{
8✔
856
  Json query = Json::object{
8✔
857
    {"method", "directBackendCmd"},
8✔
858
    {"parameters", Json::object{{"query", querystr}}}};
8✔
859

860
  Json answer;
8✔
861
  if (!this->send(query) || !this->recv(answer)) {
8!
862
    return "backend command failed";
×
863
  }
×
864

865
  return asString(answer["result"]);
8✔
866
}
8✔
867

868
bool RemoteBackend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
869
{
×
870
  const auto intMax = static_cast<decltype(maxResults)>(std::numeric_limits<int>::max());
×
871
  if (maxResults > intMax) {
×
872
    throw std::out_of_range("Remote backend: length of list of result (" + std::to_string(maxResults) + ") is larger than what the JSON library supports for serialization (" + std::to_string(intMax) + ")");
×
873
  }
×
874

875
  Json query = Json::object{
×
876
    {"method", "searchRecords"},
×
877
    {"parameters", Json::object{{"pattern", pattern}, {"maxResults", static_cast<int>(maxResults)}}}};
×
878

879
  Json answer;
×
880
  if (!this->send(query) || !this->recv(answer)) {
×
881
    return false;
×
882
  }
×
883

884
  if (!answer["result"].is_array()) {
×
885
    return false;
×
886
  }
×
887

888
  for (const auto& row : answer["result"].array_items()) {
×
889
    DNSResourceRecord rr;
×
890
    rr.qtype = stringFromJson(row, "qtype");
×
891
    rr.qname = DNSName(stringFromJson(row, "qname"));
×
892
    rr.qclass = QClass::IN;
×
893
    rr.content = stringFromJson(row, "content");
×
894
    rr.ttl = row["ttl"].int_value();
×
895
    rr.domain_id = static_cast<domainid_t>(intFromJson(row, "domain_id", UnknownDomainID));
×
896
    if (d_dnssec) {
×
897
      rr.auth = (intFromJson(row, "auth", 1) != 0);
×
898
    }
×
899
    else {
×
900
      rr.auth = 1;
×
901
    }
×
902
    rr.scopeMask = row["scopeMask"].int_value();
×
903
    result.push_back(rr);
×
904
  }
×
905

906
  return true;
×
907
}
×
908

909
bool RemoteBackend::searchComments(const string& /* pattern */, size_t /* maxResults */, vector<Comment>& /* result */)
910
{
×
911
  // FIXME: Implement Comment API
912
  return false;
×
913
}
×
914

915
void RemoteBackend::getAllDomains(vector<DomainInfo>* domains, bool /* getSerial */, bool include_disabled)
916
{
×
917
  Json query = Json::object{
×
918
    {"method", "getAllDomains"},
×
919
    {"parameters", Json::object{{"include_disabled", include_disabled}}}};
×
920

921
  Json answer;
×
922
  if (!this->send(query) || !this->recv(answer)) {
×
923
    return;
×
924
  }
×
925

926
  if (!answer["result"].is_array()) {
×
927
    return;
×
928
  }
×
929

930
  for (const auto& row : answer["result"].array_items()) {
×
931
    DomainInfo di;
×
932
    this->parseDomainInfo(row, di);
×
933
    domains->push_back(di);
×
934
  }
×
935
}
×
936

937
void RemoteBackend::getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
938
{
×
939
  Json query = Json::object{
×
940
    {"method", "getUpdatedMasters"},
×
941
    {"parameters", Json::object{}},
×
942
  };
×
943

944
  Json answer;
×
945
  if (!this->send(query) || !this->recv(answer)) {
×
946
    return;
×
947
  }
×
948

949
  if (!answer["result"].is_array()) {
×
950
    return;
×
951
  }
×
952

953
  for (const auto& row : answer["result"].array_items()) {
×
954
    DomainInfo di;
×
955
    this->parseDomainInfo(row, di);
×
956
    domains.push_back(di);
×
957
  }
×
958
}
×
959

960
void RemoteBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
961
{
×
962
  Json query = Json::object{
×
963
    {"method", "getUnfreshSlaveInfos"},
×
964
    {"parameters", Json::object{}},
×
965
  };
×
966

967
  Json answer;
×
968
  if (!this->send(query) || !this->recv(answer)) {
×
969
    return;
×
970
  }
×
971

972
  if (!answer["result"].is_array()) {
×
973
    return;
×
974
  }
×
975

976
  for (const auto& row : answer["result"].array_items()) {
×
977
    DomainInfo di;
×
978
    this->parseDomainInfo(row, di);
×
979
    domains->push_back(di);
×
980
  }
×
981
}
×
982

983
void RemoteBackend::setStale(domainid_t domain_id)
984
{
×
985
  Json query = Json::object{
×
986
    {"method", "setStale"},
×
987
    {"parameters", Json::object{{"id", domain_id}}}};
×
988

989
  Json answer;
×
990
  if (!this->send(query) || !this->recv(answer)) {
×
NEW
991
    SLOG(g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setStale(" << domain_id << ")" << endl,
×
NEW
992
         d_slog->info(Logr::Error, "Failed to execute RPC for RemoteBackend::setStale", "domain_id", Logging::Loggable(domain_id)));
×
993
  }
×
994
}
×
995

996
void RemoteBackend::setFresh(domainid_t domain_id)
997
{
×
998
  Json query = Json::object{
×
999
    {"method", "setFresh"},
×
1000
    {"parameters", Json::object{{"id", domain_id}}}};
×
1001

1002
  Json answer;
×
1003
  if (!this->send(query) || !this->recv(answer)) {
×
NEW
1004
    SLOG(g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setFresh(" << domain_id << ")" << endl,
×
NEW
1005
         d_slog->info(Logr::Error, "Failed to execute RPC for RemoteBackend::setFresh", "domain_id", Logging::Loggable(domain_id)));
×
1006
  }
×
1007
}
×
1008

1009
class RemoteBackendFactory : public BackendFactory
1010
{
1011
public:
1012
  RemoteBackendFactory() :
1013
    BackendFactory("remote") {}
5,904✔
1014

1015
  void declareArguments(const std::string& suffix = "") override
1016
  {
32✔
1017
    declare(suffix, "dnssec", "Enable dnssec support", "no");
32✔
1018
    declare(suffix, "connection-string", "Connection string", "");
32✔
1019
  }
32✔
1020

1021
  DNSBackend* make(const std::string& suffix = "") override
1022
  {
92✔
1023
    return new RemoteBackend(suffix);
92✔
1024
  }
92✔
1025
};
1026

1027
class RemoteLoader
1028
{
1029
public:
1030
  RemoteLoader();
1031
};
1032

1033
RemoteLoader::RemoteLoader()
1034
{
5,904✔
1035
  BackendMakers().report(std::make_unique<RemoteBackendFactory>());
5,904✔
1036
  // If this module is not loaded dynamically at runtime, this code runs
1037
  // as part of a global constructor, before the structured logger has a
1038
  // chance to be set up, so fallback to simple logging in this case.
1039
  if (!g_slogStructured || !g_slog) {
5,904!
1040
    g_log << Logger::Info << kBackendId << " This is the remote backend version " VERSION
5,904✔
1041
#ifndef REPRODUCIBLE
5,904✔
1042
          << " (" __DATE__ " " __TIME__ ")"
5,904✔
1043
#endif
5,904✔
1044
          << " reporting" << endl;
5,904✔
1045
  }
5,904✔
NEW
1046
  else {
×
NEW
1047
    g_slog->withName("remotebackend")->info(Logr::Info, "remote backend starting", "version", Logging::Loggable(VERSION)
×
NEW
1048
#ifndef REPRODUCIBLE
×
NEW
1049
                                                                                                ,
×
NEW
1050
                                            "build date", Logging::Loggable(__DATE__ " " __TIME__)
×
NEW
1051
#endif
×
NEW
1052
    );
×
NEW
1053
  }
×
1054
}
5,904✔
1055

1056
static RemoteLoader remoteloader;
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