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

PowerDNS / pdns / 18370591226

09 Oct 2025 08:40AM UTC coverage: 64.094% (-0.04%) from 64.136%
18370591226

Pull #16224

github

web-flow
Merge b58891300 into 152db0df0
Pull Request #16224: dnsdist: Fix a typo in the XSK documentation

42757 of 101504 branches covered (42.12%)

Branch coverage included in aggregate %.

129859 of 167814 relevant lines covered (77.38%)

5755713.48 hits per line

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

34.72
/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
      g_log << Logger::Info << "[remotebackend]: " << message.string_value() << std::endl;
103✔
66
    }
103✔
67
    return retval;
640✔
68
  }
640✔
69
  throw PDNSException("Unknown error while receiving data");
×
70
}
640✔
71

72
void RemoteBackend::makeErrorAndThrow(Json& value)
73
{
×
74
  std::string msg = "Remote process indicated a failure";
×
75
  for (const auto& message : value["log"].array_items()) {
×
76
    msg += " '" + message.string_value() + "'";
×
77
  }
×
78
  throw PDNSException(msg);
×
79
}
×
80

81
/**
82
 * Standard ctor and dtor
83
 */
84
RemoteBackend::RemoteBackend(const std::string& suffix)
85
{
92✔
86
  setArgPrefix("remote" + suffix);
92✔
87

88
  this->d_connstr = getArg("connection-string");
92✔
89
  this->d_dnssec = mustDo("dnssec");
92✔
90

91
  build();
92✔
92
}
92✔
93

94
RemoteBackend::~RemoteBackend() = default;
76✔
95

96
bool RemoteBackend::send(Json& value)
97
{
581✔
98
  try {
581✔
99
    if (!connector->send(value)) {
581!
100
      // XXX does this work work even though we throw?
101
      this->connector.reset();
×
102
      build();
×
103
      throw DBException("Could not send a message to remote process");
×
104
    }
×
105
  }
581✔
106
  catch (const PDNSException& ex) {
581✔
107
    throw DBException("Exception caught when sending: " + ex.reason);
×
108
  }
×
109
  return true;
581✔
110
}
581✔
111

112
bool RemoteBackend::recv(Json& value)
113
{
581✔
114
  try {
581✔
115
    return connector->recv(value);
581✔
116
  }
581✔
117
  catch (const PDNSException& ex) {
581✔
118
    this->connector.reset();
×
119
    build();
×
120
    throw DBException("Exception caught when receiving: " + ex.reason);
×
121
  }
×
122
  catch (const std::exception& e) {
581✔
123
    this->connector.reset();
×
124
    build();
×
125
    throw DBException("Exception caught when receiving: " + std::string(e.what()));
×
126
  }
×
127
}
581✔
128

129
/**
130
 * Builds connector based on options
131
 * Currently supports unix,pipe and http
132
 */
133
int RemoteBackend::build()
134
{
92✔
135
  std::vector<std::string> parts;
92✔
136
  std::string type;
92✔
137
  std::string opts;
92✔
138
  std::map<std::string, std::string> options;
92✔
139

140
  // connstr is of format "type:options"
141
  size_t pos = 0;
92✔
142
  pos = d_connstr.find_first_of(':');
92✔
143
  if (pos == std::string::npos) {
92!
144
    throw PDNSException("Invalid connection string: malformed");
×
145
  }
×
146

147
  type = d_connstr.substr(0, pos);
92✔
148
  opts = d_connstr.substr(pos + 1);
92✔
149

150
  // tokenize the string on comma
151
  stringtok(parts, opts, ",");
92✔
152

153
  // find out some options and parse them while we're at it
154
  for (const auto& opt : parts) {
140✔
155
    std::string key;
140✔
156
    std::string val;
140✔
157
    // make sure there is something else than air in the option...
158
    if (opt.find_first_not_of(" ") == std::string::npos) {
140!
159
      continue;
×
160
    }
×
161

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

165
    if (pos == std::string::npos) {
140!
166
      key = opt;
×
167
      val = "yes";
×
168
    }
×
169
    else {
140✔
170
      key = opt.substr(0, pos);
140✔
171
      val = opt.substr(pos + 1);
140✔
172
    }
140✔
173
    options[key] = std::move(val);
140✔
174
  }
140✔
175

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

197
  return -1;
92✔
198
}
92✔
199

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

210
  string localIP = "0.0.0.0";
148✔
211
  string remoteIP = "0.0.0.0";
148✔
212
  string realRemote = "0.0.0.0/0";
148✔
213

214
  if (pkt_p != nullptr) {
148✔
215
    localIP = pkt_p->getLocal().toString();
56✔
216
    realRemote = pkt_p->getRealRemote().toString();
56✔
217
    remoteIP = pkt_p->getInnerRemote().toString();
56✔
218
  }
56✔
219

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

224
  if (!this->send(query) || !this->recv(d_result)) {
148!
225
    return;
×
226
  }
×
227

228
  // OK. we have result parameters in result. do not process empty result.
229
  if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
148!
230
    return;
60✔
231
  }
60✔
232

233
  d_index = 0;
88✔
234
}
88✔
235

236
// Similar to lookup above, but passes an extra include_disabled parameter.
237
void RemoteBackend::APILookup(const QType& qtype, const DNSName& qdomain, domainid_t zoneId, bool include_disabled)
238
{
×
239
  if (d_index != -1) {
×
240
    throw PDNSException("Attempt to lookup while one running");
×
241
  }
×
242

243
  string localIP = "0.0.0.0";
×
244
  string remoteIP = "0.0.0.0";
×
245
  string realRemote = "0.0.0.0/0";
×
246

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

251
  if (!this->send(query) || !this->recv(d_result)) {
×
252
    return;
×
253
  }
×
254

255
  // OK. we have result parameters in result. do not process empty result.
256
  if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
×
257
    return;
×
258
  }
×
259

260
  d_index = 0;
×
261
}
×
262

263
bool RemoteBackend::list(const ZoneName& target, domainid_t domain_id, bool include_disabled)
264
{
16✔
265
  if (d_index != -1) {
16!
266
    throw PDNSException("Attempt to lookup while one running");
×
267
  }
×
268

269
  Json query = Json::object{
16✔
270
    {"method", "list"},
16✔
271
    {"parameters", Json::object{{"zonename", target.toString()}, {"domain_id", domain_id}, {"include_disabled", include_disabled}}}};
16✔
272

273
  if (!this->send(query) || !this->recv(d_result)) {
16!
274
    return false;
×
275
  }
×
276
  if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
16!
277
    return false;
×
278
  }
×
279

280
  d_index = 0;
16✔
281
  return true;
16✔
282
}
16✔
283

284
bool RemoteBackend::get(DNSResourceRecord& rr)
285
{
478✔
286
  if (d_index == -1) {
478✔
287
    return false;
164✔
288
  }
164✔
289

290
  rr.qtype = stringFromJson(d_result["result"][d_index], "qtype");
314✔
291
  rr.qname = DNSName(stringFromJson(d_result["result"][d_index], "qname"));
314✔
292
  rr.qclass = QClass::IN;
314✔
293
  rr.content = stringFromJson(d_result["result"][d_index], "content");
314✔
294
  rr.ttl = d_result["result"][d_index]["ttl"].int_value();
314✔
295
  rr.domain_id = static_cast<domainid_t>(intFromJson(d_result["result"][d_index], "domain_id", UnknownDomainID));
314✔
296
  if (d_dnssec) {
314✔
297
    rr.auth = (intFromJson(d_result["result"][d_index], "auth", 1) != 0);
216✔
298
  }
216✔
299
  else {
98✔
300
    rr.auth = true;
98✔
301
  }
98✔
302
  rr.scopeMask = d_result["result"][d_index]["scopeMask"].int_value();
314✔
303
  d_index++;
314✔
304

305
  // id index is out of bounds, we know the results end here.
306
  if (d_index == static_cast<int>(d_result["result"].array_items().size())) {
314✔
307
    d_result = Json();
104✔
308
    d_index = -1;
104✔
309
  }
104✔
310
  return true;
314✔
311
}
478✔
312

313
// NOLINTNEXTLINE(readability-identifier-length)
314
bool RemoteBackend::getBeforeAndAfterNamesAbsolute(domainid_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
315
{
261✔
316
  // no point doing dnssec if it's not supported
317
  if (!d_dnssec) {
261!
318
    return false;
×
319
  }
×
320

321
  Json query = Json::object{
261✔
322
    {"method", "getBeforeAndAfterNamesAbsolute"},
261✔
323
    {"parameters", Json::object{{"id", Json(id)}, {"qname", qname.toString()}}}};
261✔
324
  Json answer;
261✔
325

326
  if (!this->send(query) || !this->recv(answer)) {
261!
327
    return false;
×
328
  }
×
329

330
  unhashed = DNSName(stringFromJson(answer["result"], "unhashed"));
261✔
331
  before.clear();
261✔
332
  after.clear();
261✔
333
  if (answer["result"]["before"] != Json()) {
261!
334
    before = DNSName(stringFromJson(answer["result"], "before"));
261✔
335
  }
261✔
336
  if (answer["result"]["after"] != Json()) {
261!
337
    after = DNSName(stringFromJson(answer["result"], "after"));
261✔
338
  }
261✔
339

340
  return true;
261✔
341
}
261✔
342

343
bool RemoteBackend::getAllDomainMetadata(const ZoneName& name, std::map<std::string, std::vector<std::string>>& meta)
344
{
48✔
345
  Json query = Json::object{
48✔
346
    {"method", "getAllDomainMetadata"},
48✔
347
    {"parameters", Json::object{{"name", name.toString()}}}};
48✔
348

349
  if (!this->send(query)) {
48!
350
    return false;
×
351
  }
×
352

353
  meta.clear();
48✔
354

355
  Json answer;
48✔
356
  // not mandatory to implement
357
  if (!this->recv(answer)) {
48!
358
    return true;
×
359
  }
×
360

361
  for (const auto& pair : answer["result"].object_items()) {
48!
362
    if (pair.second.is_array()) {
×
363
      for (const auto& val : pair.second.array_items()) {
×
364
        meta[pair.first].push_back(asString(val));
×
365
      }
×
366
    }
×
367
    else {
×
368
      meta[pair.first].push_back(asString(pair.second));
×
369
    }
×
370
  }
×
371

372
  return true;
48✔
373
}
48✔
374

375
bool RemoteBackend::getDomainMetadata(const ZoneName& name, const std::string& kind, std::vector<std::string>& meta)
376
{
16✔
377
  Json query = Json::object{
16✔
378
    {"method", "getDomainMetadata"},
16✔
379
    {"parameters", Json::object{{"name", name.toString()}, {"kind", kind}}}};
16✔
380

381
  if (!this->send(query)) {
16!
382
    return false;
×
383
  }
×
384

385
  meta.clear();
16✔
386

387
  Json answer;
16✔
388
  // not mandatory to implement
389
  if (!this->recv(answer)) {
16!
390
    return true;
×
391
  }
×
392

393
  if (answer["result"].is_array()) {
16!
394
    for (const auto& row : answer["result"].array_items()) {
16!
395
      meta.push_back(row.string_value());
×
396
    }
×
397
  }
16✔
398
  else if (answer["result"].is_string()) {
×
399
    meta.push_back(answer["result"].string_value());
×
400
  }
×
401

402
  return true;
16✔
403
}
16✔
404

405
bool RemoteBackend::setDomainMetadata(const ZoneName& name, const std::string& kind, const std::vector<std::string>& meta)
406
{
×
407
  Json query = Json::object{
×
408
    {"method", "setDomainMetadata"},
×
409
    {"parameters", Json::object{{"name", name.toString()}, {"kind", kind}, {"value", meta}}}};
×
410

411
  Json answer;
×
412
  if (!this->send(query) || !this->recv(answer)) {
×
413
    return false;
×
414
  }
×
415

416
  return boolFromJson(answer, "result", false);
×
417
}
×
418

419
bool RemoteBackend::getDomainKeys(const ZoneName& name, std::vector<DNSBackend::KeyData>& keys)
420
{
48✔
421
  // no point doing dnssec if it's not supported
422
  if (!d_dnssec) {
48✔
423
    return false;
12✔
424
  }
12✔
425

426
  Json query = Json::object{
36✔
427
    {"method", "getDomainKeys"},
36✔
428
    {"parameters", Json::object{{"name", name.toString()}}}};
36✔
429

430
  Json answer;
36✔
431
  if (!this->send(query) || !this->recv(answer)) {
36!
432
    return false;
8✔
433
  }
8✔
434

435
  keys.clear();
28✔
436

437
  for (const auto& jsonKey : answer["result"].array_items()) {
28✔
438
    DNSBackend::KeyData key;
28✔
439
    key.id = intFromJson(jsonKey, "id");
28✔
440
    key.flags = intFromJson(jsonKey, "flags");
28✔
441
    key.active = asBool(jsonKey["active"]);
28✔
442
    key.published = boolFromJson(jsonKey, "published", true);
28✔
443
    key.content = stringFromJson(jsonKey, "content");
28✔
444
    keys.emplace_back(std::move(key));
28✔
445
  }
28✔
446

447
  return true;
28✔
448
}
36✔
449

450
bool RemoteBackend::removeDomainKey(const ZoneName& name, unsigned int keyId)
451
{
×
452
  // no point doing dnssec if it's not supported
453
  if (!d_dnssec) {
×
454
    return false;
×
455
  }
×
456

457
  Json query = Json::object{
×
458
    {"method", "removeDomainKey"},
×
459
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(keyId)}}}};
×
460

461
  Json answer;
×
462
  return this->send(query) && this->recv(answer);
×
463
}
×
464

465
bool RemoteBackend::addDomainKey(const ZoneName& name, const KeyData& key, int64_t& keyId)
466
{
8✔
467
  // no point doing dnssec if it's not supported
468
  if (!d_dnssec) {
8!
469
    return false;
×
470
  }
×
471

472
  Json query = Json::object{
8✔
473
    {"method", "addDomainKey"},
8✔
474
    {"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✔
475

476
  Json answer;
8✔
477
  if (!this->send(query) || !this->recv(answer)) {
8!
478
    return false;
×
479
  }
×
480

481
  keyId = answer["result"].int_value();
8✔
482
  return keyId >= 0;
8✔
483
}
8✔
484

485
bool RemoteBackend::activateDomainKey(const ZoneName& name, unsigned int keyId)
486
{
×
487
  // no point doing dnssec if it's not supported
488
  if (!d_dnssec) {
×
489
    return false;
×
490
  }
×
491

492
  Json query = Json::object{
×
493
    {"method", "activateDomainKey"},
×
494
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(keyId)}}}};
×
495

496
  Json answer;
×
497
  return this->send(query) && this->recv(answer);
×
498
}
×
499

500
bool RemoteBackend::deactivateDomainKey(const ZoneName& name, unsigned int keyId)
501
{
×
502
  // no point doing dnssec if it's not supported
503
  if (!d_dnssec) {
×
504
    return false;
×
505
  }
×
506

507
  Json query = Json::object{
×
508
    {"method", "deactivateDomainKey"},
×
509
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(keyId)}}}};
×
510

511
  Json answer;
×
512
  return this->send(query) && this->recv(answer);
×
513
}
×
514

515
bool RemoteBackend::publishDomainKey(const ZoneName& name, unsigned int keyId)
516
{
×
517
  // no point doing dnssec if it's not supported
518
  if (!d_dnssec) {
×
519
    return false;
×
520
  }
×
521

522
  Json query = Json::object{
×
523
    {"method", "publishDomainKey"},
×
524
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(keyId)}}}};
×
525

526
  Json answer;
×
527
  return this->send(query) && this->recv(answer);
×
528
}
×
529

530
bool RemoteBackend::unpublishDomainKey(const ZoneName& name, unsigned int keyId)
531
{
×
532
  // no point doing dnssec if it's not supported
533
  if (!d_dnssec) {
×
534
    return false;
×
535
  }
×
536

537
  Json query = Json::object{
×
538
    {"method", "unpublishDomainKey"},
×
539
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(keyId)}}}};
×
540

541
  Json answer;
×
542
  return this->send(query) && this->recv(answer);
×
543
}
×
544

545
unsigned int RemoteBackend::getCapabilities()
546
{
233✔
547
  unsigned int caps = CAP_DIRECT | CAP_LIST | CAP_SEARCH;
233✔
548
  if (d_dnssec) {
233✔
549
    caps |= CAP_DNSSEC;
229✔
550
  }
229✔
551
  return caps;
233✔
552
}
233✔
553

554
bool RemoteBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, std::string& content)
555
{
×
556
  // no point doing dnssec if it's not supported
557
  if (!d_dnssec) {
×
558
    return false;
×
559
  }
×
560

561
  Json query = Json::object{
×
562
    {"method", "getTSIGKey"},
×
563
    {"parameters", Json::object{{"name", name.toString()}}}};
×
564

565
  Json answer;
×
566
  if (!this->send(query) || !this->recv(answer)) {
×
567
    return false;
×
568
  }
×
569

570
  algorithm = DNSName(stringFromJson(answer["result"], "algorithm"));
×
571
  content = stringFromJson(answer["result"], "content");
×
572

573
  return true;
×
574
}
×
575

576
bool RemoteBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const std::string& content)
577
{
×
578
  // no point doing dnssec if it's not supported
579
  if (!d_dnssec) {
×
580
    return false;
×
581
  }
×
582

583
  Json query = Json::object{
×
584
    {"method", "setTSIGKey"},
×
585
    {"parameters", Json::object{{"name", name.toString()}, {"algorithm", algorithm.toString()}, {"content", content}}}};
×
586

587
  Json answer;
×
588
  return connector->send(query) && connector->recv(answer);
×
589
}
×
590

591
bool RemoteBackend::deleteTSIGKey(const DNSName& name)
592
{
×
593
  // no point doing dnssec if it's not supported
594
  if (!d_dnssec) {
×
595
    return false;
×
596
  }
×
597
  Json query = Json::object{
×
598
    {"method", "deleteTSIGKey"},
×
599
    {"parameters", Json::object{{"name", name.toString()}}}};
×
600

601
  Json answer;
×
602
  return connector->send(query) && connector->recv(answer);
×
603
}
×
604

605
bool RemoteBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
606
{
×
607
  // no point doing dnssec if it's not supported
608
  if (!d_dnssec) {
×
609
    return false;
×
610
  }
×
611
  Json query = Json::object{
×
612
    {"method", "getTSIGKeys"},
×
613
    {"parameters", Json::object{}}};
×
614

615
  Json answer;
×
616
  if (!connector->send(query) || !connector->recv(answer)) {
×
617
    return false;
×
618
  }
×
619

620
  for (const auto& jsonKey : answer["result"].array_items()) {
×
621
    struct TSIGKey key;
×
622
    key.name = DNSName(stringFromJson(jsonKey, "name"));
×
623
    key.algorithm = DNSName(stringFromJson(jsonKey, "algorithm"));
×
624
    key.key = stringFromJson(jsonKey, "content");
×
625
    keys.push_back(key);
×
626
  }
×
627

628
  return true;
×
629
}
×
630

631
void RemoteBackend::parseDomainInfo(const Json& obj, DomainInfo& di)
632
{
24✔
633
  di.id = static_cast<domainid_t>(intFromJson(obj, "id", UnknownDomainID));
24✔
634
  di.zone = ZoneName(stringFromJson(obj, "zone"));
24✔
635
  for (const auto& primary : obj["masters"].array_items()) {
24!
636
    di.primaries.emplace_back(primary.string_value(), 53);
×
637
  }
×
638

639
  di.notified_serial = static_cast<unsigned int>(doubleFromJson(obj, "notified_serial", 0));
24✔
640
  di.serial = static_cast<unsigned int>(obj["serial"].number_value());
24✔
641
  di.last_check = static_cast<time_t>(obj["last_check"].number_value());
24✔
642

643
  string kind;
24✔
644
  if (obj["kind"].is_string()) {
24!
645
    kind = stringFromJson(obj, "kind");
24✔
646
  }
24✔
647
  if (kind == "master") {
24!
648
    di.kind = DomainInfo::Primary;
×
649
  }
×
650
  else if (kind == "slave") {
24!
651
    di.kind = DomainInfo::Secondary;
×
652
  }
×
653
  else {
24✔
654
    di.kind = DomainInfo::Native;
24✔
655
  }
24✔
656
  di.backend = this;
24✔
657
}
24✔
658

659
bool RemoteBackend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool /* getSerial */)
660
{
24✔
661
  if (domain.empty()) {
24!
662
    return false;
×
663
  }
×
664

665
  Json query = Json::object{
24✔
666
    {"method", "getDomainInfo"},
24✔
667
    {"parameters", Json::object{{"name", domain.toString()}}}};
24✔
668

669
  Json answer;
24✔
670
  if (!this->send(query) || !this->recv(answer)) {
24!
671
    return false;
×
672
  }
×
673

674
  this->parseDomainInfo(answer["result"], info);
24✔
675
  return true;
24✔
676
}
24✔
677

678
// NOLINTNEXTLINE(readability-identifier-length)
679
void RemoteBackend::setNotified(domainid_t id, uint32_t serial)
680
{
×
681
  Json query = Json::object{
×
682
    {"method", "setNotified"},
×
683
    {"parameters", Json::object{{"id", id}, {"serial", static_cast<double>(serial)}}}};
×
684

685
  Json answer;
×
686
  if (!this->send(query) || !this->recv(answer)) {
×
687
    g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setNotified(" << id << "," << serial << ")" << endl;
×
688
  }
×
689
}
×
690

691
bool RemoteBackend::autoPrimaryBackend(const string& ipAddress, const ZoneName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** ddb)
692
{
×
693
  Json::array rrset;
×
694

695
  for (const auto& ns : nsset) {
×
696
    rrset.push_back(Json::object{
×
697
      {"qtype", ns.qtype.toString()},
×
698
      {"qname", ns.qname.toString()},
×
699
      {"qclass", QClass::IN.getCode()},
×
700
      {"content", ns.content},
×
701
      {"ttl", static_cast<int>(ns.ttl)},
×
702
      {"auth", ns.auth}});
×
703
  }
×
704

705
  Json query = Json::object{
×
706
    {"method", "superMasterBackend"},
×
707
    {"parameters", Json::object{{"ip", ipAddress}, {"domain", domain.toString()}, {"nsset", rrset}}}};
×
708

709
  *ddb = nullptr;
×
710

711
  Json answer;
×
712
  if (!this->send(query) || !this->recv(answer)) {
×
713
    return false;
×
714
  }
×
715

716
  // we are the backend
717
  *ddb = this;
×
718

719
  // we allow simple true as well...
720
  if (answer["result"].is_object()) {
×
721
    *account = stringFromJson(answer["result"], "account");
×
722
    *nameserver = stringFromJson(answer["result"], "nameserver");
×
723
  }
×
724

725
  return true;
×
726
}
×
727

728
bool RemoteBackend::createSecondaryDomain(const string& ipAddress, const ZoneName& domain, const string& nameserver, const string& account)
729
{
×
730
  Json query = Json::object{
×
731
    {"method", "createSlaveDomain"},
×
732
    {"parameters", Json::object{
×
733
                     {"ip", ipAddress},
×
734
                     {"domain", domain.toString()},
×
735
                     {"nameserver", nameserver},
×
736
                     {"account", account},
×
737
                   }}};
×
738

739
  Json answer;
×
740
  return this->send(query) && this->recv(answer);
×
741
}
×
742

743
bool RemoteBackend::replaceRRSet(domainid_t domain_id, const DNSName& qname, const QType& qtype, const vector<DNSResourceRecord>& rrset)
744
{
×
745
  Json::array json_rrset;
×
746
  for (const auto& rr : rrset) {
×
747
    json_rrset.push_back(Json::object{
×
748
      {"qtype", rr.qtype.toString()},
×
749
      {"qname", rr.qname.toString()},
×
750
      {"qclass", QClass::IN.getCode()},
×
751
      {"content", rr.content},
×
752
      {"ttl", static_cast<int>(rr.ttl)},
×
753
      {"auth", rr.auth}});
×
754
  }
×
755

756
  Json query = Json::object{
×
757
    {"method", "replaceRRSet"},
×
758
    {"parameters", Json::object{{"domain_id", domain_id}, {"qname", qname.toString()}, {"qtype", qtype.toString()}, {"trxid", static_cast<double>(d_trxid)}, {"rrset", json_rrset}}}};
×
759

760
  Json answer;
×
761
  return this->send(query) && this->recv(answer);
×
762
}
×
763

764
bool RemoteBackend::feedRecord(const DNSResourceRecord& rr, const DNSName& ordername, bool /* ordernameIsNSEC3 */)
765
{
×
766
  Json query = Json::object{
×
767
    {"method", "feedRecord"},
×
768
    {"parameters", Json::object{
×
769
                     {"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())}}},
×
770
                     {"trxid", static_cast<double>(d_trxid)},
×
771
                   }}};
×
772

773
  Json answer;
×
774
  return this->send(query) && this->recv(answer); // XXX FIXME this API should not return 'true' I think -ahu
×
775
}
×
776

777
bool RemoteBackend::feedEnts(domainid_t domain_id, map<DNSName, bool>& nonterm)
778
{
×
779
  Json::array nts;
×
780

781
  for (const auto& t : nonterm) {
×
782
    nts.push_back(Json::object{
×
783
      {"nonterm", t.first.toString()},
×
784
      {"auth", t.second}});
×
785
  }
×
786

787
  Json query = Json::object{
×
788
    {"method", "feedEnts"},
×
789
    {"parameters", Json::object{{"domain_id", domain_id}, {"trxid", static_cast<double>(d_trxid)}, {"nonterm", nts}}},
×
790
  };
×
791

792
  Json answer;
×
793
  return this->send(query) && this->recv(answer);
×
794
}
×
795

796
bool RemoteBackend::feedEnts3(domainid_t domain_id, const DNSName& domain, map<DNSName, bool>& nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow)
797
{
×
798
  Json::array nts;
×
799

800
  for (const auto& t : nonterm) {
×
801
    nts.push_back(Json::object{
×
802
      {"nonterm", t.first.toString()},
×
803
      {"auth", t.second}});
×
804
  }
×
805

806
  Json query = Json::object{
×
807
    {"method", "feedEnts3"},
×
808
    {"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}}},
×
809
  };
×
810

811
  Json answer;
×
812
  return this->send(query) && this->recv(answer);
×
813
}
×
814

815
bool RemoteBackend::startTransaction(const ZoneName& domain, domainid_t domain_id)
816
{
16✔
817
  this->d_trxid = time((time_t*)nullptr);
16✔
818

819
  Json query = Json::object{
16✔
820
    {"method", "startTransaction"},
16✔
821
    {"parameters", Json::object{{"domain", domain.toString()}, {"domain_id", domain_id}, {"trxid", static_cast<double>(d_trxid)}}}};
16✔
822

823
  Json answer;
16✔
824
  if (!this->send(query) || !this->recv(answer)) {
16!
825
    d_trxid = -1;
16✔
826
    return false;
16✔
827
  }
16✔
828
  return true;
×
829
}
16✔
830

831
bool RemoteBackend::commitTransaction()
832
{
16✔
833
  if (d_trxid == -1) {
16!
834
    return false;
16✔
835
  }
16✔
836

837
  Json query = Json::object{
×
838
    {"method", "commitTransaction"},
×
839
    {"parameters", Json::object{{"trxid", static_cast<double>(d_trxid)}}}};
×
840

841
  d_trxid = -1;
×
842
  Json answer;
×
843
  return this->send(query) && this->recv(answer);
×
844
}
16✔
845

846
bool RemoteBackend::abortTransaction()
847
{
×
848
  if (d_trxid == -1) {
×
849
    return false;
×
850
  }
×
851

852
  Json query = Json::object{
×
853
    {"method", "abortTransaction"},
×
854
    {"parameters", Json::object{{"trxid", static_cast<double>(d_trxid)}}}};
×
855

856
  d_trxid = -1;
×
857
  Json answer;
×
858
  return this->send(query) && this->recv(answer);
×
859
}
×
860

861
string RemoteBackend::directBackendCmd(const string& querystr)
862
{
8✔
863
  Json query = Json::object{
8✔
864
    {"method", "directBackendCmd"},
8✔
865
    {"parameters", Json::object{{"query", querystr}}}};
8✔
866

867
  Json answer;
8✔
868
  if (!this->send(query) || !this->recv(answer)) {
8!
869
    return "backend command failed";
×
870
  }
×
871

872
  return asString(answer["result"]);
8✔
873
}
8✔
874

875
bool RemoteBackend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
876
{
×
877
  const auto intMax = static_cast<decltype(maxResults)>(std::numeric_limits<int>::max());
×
878
  if (maxResults > intMax) {
×
879
    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) + ")");
×
880
  }
×
881

882
  Json query = Json::object{
×
883
    {"method", "searchRecords"},
×
884
    {"parameters", Json::object{{"pattern", pattern}, {"maxResults", static_cast<int>(maxResults)}}}};
×
885

886
  Json answer;
×
887
  if (!this->send(query) || !this->recv(answer)) {
×
888
    return false;
×
889
  }
×
890

891
  if (!answer["result"].is_array()) {
×
892
    return false;
×
893
  }
×
894

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

913
  return true;
×
914
}
×
915

916
bool RemoteBackend::searchComments(const string& /* pattern */, size_t /* maxResults */, vector<Comment>& /* result */)
917
{
×
918
  // FIXME: Implement Comment API
919
  return false;
×
920
}
×
921

922
void RemoteBackend::getAllDomains(vector<DomainInfo>* domains, bool /* getSerial */, bool include_disabled)
923
{
×
924
  Json query = Json::object{
×
925
    {"method", "getAllDomains"},
×
926
    {"parameters", Json::object{{"include_disabled", include_disabled}}}};
×
927

928
  Json answer;
×
929
  if (!this->send(query) || !this->recv(answer)) {
×
930
    return;
×
931
  }
×
932

933
  if (!answer["result"].is_array()) {
×
934
    return;
×
935
  }
×
936

937
  for (const auto& row : answer["result"].array_items()) {
×
938
    DomainInfo di;
×
939
    this->parseDomainInfo(row, di);
×
940
    domains->push_back(di);
×
941
  }
×
942
}
×
943

944
void RemoteBackend::getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
945
{
×
946
  Json query = Json::object{
×
947
    {"method", "getUpdatedMasters"},
×
948
    {"parameters", Json::object{}},
×
949
  };
×
950

951
  Json answer;
×
952
  if (!this->send(query) || !this->recv(answer)) {
×
953
    return;
×
954
  }
×
955

956
  if (!answer["result"].is_array()) {
×
957
    return;
×
958
  }
×
959

960
  for (const auto& row : answer["result"].array_items()) {
×
961
    DomainInfo di;
×
962
    this->parseDomainInfo(row, di);
×
963
    domains.push_back(di);
×
964
  }
×
965
}
×
966

967
void RemoteBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
968
{
×
969
  Json query = Json::object{
×
970
    {"method", "getUnfreshSlaveInfos"},
×
971
    {"parameters", Json::object{}},
×
972
  };
×
973

974
  Json answer;
×
975
  if (!this->send(query) || !this->recv(answer)) {
×
976
    return;
×
977
  }
×
978

979
  if (!answer["result"].is_array()) {
×
980
    return;
×
981
  }
×
982

983
  for (const auto& row : answer["result"].array_items()) {
×
984
    DomainInfo di;
×
985
    this->parseDomainInfo(row, di);
×
986
    domains->push_back(di);
×
987
  }
×
988
}
×
989

990
void RemoteBackend::setStale(domainid_t domain_id)
991
{
×
992
  Json query = Json::object{
×
993
    {"method", "setStale"},
×
994
    {"parameters", Json::object{{"id", domain_id}}}};
×
995

996
  Json answer;
×
997
  if (!this->send(query) || !this->recv(answer)) {
×
998
    g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setStale(" << domain_id << ")" << endl;
×
999
  }
×
1000
}
×
1001

1002
void RemoteBackend::setFresh(domainid_t domain_id)
1003
{
×
1004
  Json query = Json::object{
×
1005
    {"method", "setFresh"},
×
1006
    {"parameters", Json::object{{"id", domain_id}}}};
×
1007

1008
  Json answer;
×
1009
  if (!this->send(query) || !this->recv(answer)) {
×
1010
    g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setFresh(" << domain_id << ")" << endl;
×
1011
  }
×
1012
}
×
1013

1014
DNSBackend* RemoteBackend::maker()
1015
{
×
1016
  try {
×
1017
    return new RemoteBackend();
×
1018
  }
×
1019
  catch (...) {
×
1020
    g_log << Logger::Error << kBackendId << " Unable to instantiate a remotebackend!" << endl;
×
1021
    return nullptr;
×
1022
  };
×
1023
}
×
1024

1025
class RemoteBackendFactory : public BackendFactory
1026
{
1027
public:
1028
  RemoteBackendFactory() :
1029
    BackendFactory("remote") {}
5,869✔
1030

1031
  void declareArguments(const std::string& suffix = "") override
1032
  {
32✔
1033
    declare(suffix, "dnssec", "Enable dnssec support", "no");
32✔
1034
    declare(suffix, "connection-string", "Connection string", "");
32✔
1035
  }
32✔
1036

1037
  DNSBackend* make(const std::string& suffix = "") override
1038
  {
92✔
1039
    return new RemoteBackend(suffix);
92✔
1040
  }
92✔
1041
};
1042

1043
class RemoteLoader
1044
{
1045
public:
1046
  RemoteLoader();
1047
};
1048

1049
RemoteLoader::RemoteLoader()
1050
{
5,869✔
1051
  BackendMakers().report(std::make_unique<RemoteBackendFactory>());
5,869✔
1052
  g_log << Logger::Info << kBackendId << " This is the remote backend version " VERSION
5,869✔
1053
#ifndef REPRODUCIBLE
5,869✔
1054
        << " (" __DATE__ " " __TIME__ ")"
5,869✔
1055
#endif
5,869✔
1056
        << " reporting" << endl;
5,869✔
1057
}
5,869✔
1058

1059
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

© 2025 Coveralls, Inc