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

PowerDNS / pdns / 15920880335

26 Jun 2025 03:30PM UTC coverage: 61.923% (-3.7%) from 65.652%
15920880335

push

github

web-flow
Merge pull request #15669 from miodvallat/serial_keyer

Increase zone serial number after zone key operations

38311 of 91850 branches covered (41.71%)

Branch coverage included in aggregate %.

27 of 29 new or added lines in 1 file covered. (93.1%)

6308 existing lines in 78 files now uncovered.

120482 of 164587 relevant lines covered (73.2%)

5965233.22 hits per line

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

45.92
/pdns/recursordist/rec-system-resolve.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

23
#include <sys/types.h>
24
#include <sys/socket.h>
25
#include <netinet/in.h>
26
#include <netdb.h>
27
#include <arpa/nameser.h>
28
#include <resolv.h>
29

30
#include "dnsparser.hh"
31
#include "dnsrecords.hh"
32
#include "rec-system-resolve.hh"
33
#include "logging.hh"
34
#include "noinitvector.hh"
35
#include "threadname.hh"
36

37
namespace
38
{
39
ComboAddress resolve(const std::string& name)
40
{
2✔
41
  struct addrinfo hints = {};
2✔
42
  hints.ai_flags = AI_ADDRCONFIG;
2✔
43
  hints.ai_family = 0;
2✔
44

45
  struct addrinfo* res0 = nullptr;
2✔
46
  auto ret = getaddrinfo(name.c_str(), nullptr, &hints, &res0);
2✔
47
  // We pick the first address after sorting for now, no handling of multiple addresses or AF selection.
48
  vector<ComboAddress> vec;
2✔
49
  if (ret != 0) {
2!
50
    return {};
×
51
  }
×
52
  auto* res = res0;
2✔
53
  while (res != nullptr) {
14✔
54
    try {
12✔
55
      auto address = ComboAddress{res->ai_addr, res->ai_addrlen};
12✔
56
      vec.emplace_back(address);
12✔
57
    }
12✔
58
    catch (...) {
12✔
59
    }
×
60
    res = res->ai_next;
12✔
61
  }
12✔
62
  freeaddrinfo(res0);
2✔
63
  if (!vec.empty()) {
2!
64
    std::sort(vec.begin(), vec.end());
2✔
65
    return vec.at(0);
2✔
66
  }
2✔
67
  return {};
×
68
}
2✔
69

70
PacketBuffer resolve(const string& name, QClass cls, QType type)
71
{
×
72
  PacketBuffer answer(512);
×
73
  auto ret = res_query(name.c_str(), cls, type, answer.data(), static_cast<int>(answer.size()));
×
74
  if (ret == -1) {
×
75
    answer.resize(0);
×
76
  }
×
77
  else {
×
78
    answer.resize(ret);
×
79
  }
×
80
  return answer;
×
81
}
×
82

83
// do a id.server/CH/TXT query
84
std::string serverID()
85
{
×
86
  auto buffer = resolve("id.server", QClass::CHAOS, QType::TXT);
×
87
  if (buffer.empty()) {
×
88
    return {};
×
89
  }
×
90

91
  MOADNSParser parser(false, static_cast<const char*>(static_cast<void*>(buffer.data())), buffer.size());
×
92
  if (parser.d_header.rcode != RCode::NoError || parser.d_answers.size() != 1) {
×
93
    return {};
×
94
  }
×
95
  const auto& dnsrecord = parser.d_answers.at(0);
×
96
  if (dnsrecord.d_type == QType::TXT) {
×
97
    if (auto txt = getRR<TXTRecordContent>(dnsrecord); txt != nullptr) {
×
98
      const auto& text = txt->d_text;
×
99
      if (text.size() >= 2 && text.at(0) == '"' && text.at(text.size() - 1) == '"') {
×
100
        // remove quotes around text
101
        return txt->d_text.substr(1, txt->d_text.size() - 2);
×
102
      }
×
103
      return txt->d_text;
×
104
    }
×
105
  }
×
106
  return {};
×
107
}
×
108
} // anonymous namespace
109

110
// RecResolve class members.
111
std::string pdns::RecResolve::s_serverID;
112
time_t pdns::RecResolve::s_ttl{0};
113
time_t pdns::RecResolve::s_interval{0};
114
std::function<void()> pdns::RecResolve::s_callback;
115
bool pdns::RecResolve::s_selfResolveCheck{false};
116

117
void pdns::RecResolve::setInstanceParameters(std::string serverID, time_t ttl, time_t interval, bool selfResolveCheck, const std::function<void()>& callback)
118
{
2✔
119
  pdns::RecResolve::s_serverID = std::move(serverID);
2✔
120
  pdns::RecResolve::s_ttl = ttl;
2✔
121
  pdns::RecResolve::s_interval = interval;
2✔
122
  pdns::RecResolve::s_selfResolveCheck = selfResolveCheck;
2✔
123
  pdns::RecResolve::s_callback = callback;
2✔
124
}
2✔
125

126
pdns::RecResolve& pdns::RecResolve::getInstance()
127
{
2✔
128
  static unique_ptr<RecResolve> res = make_unique<pdns::RecResolve>(s_ttl, s_interval, s_selfResolveCheck, s_callback);
2✔
129
  return *res;
2✔
130
}
2✔
131

132
pdns::RecResolve::RecResolve(time_t ttl, time_t interval, bool selfResolveCheck, const std::function<void()>& callback) :
133
  d_ttl(ttl), d_refresher(interval, callback, selfResolveCheck, *this)
134
{
2✔
135
}
2✔
136

137
pdns::RecResolve::~RecResolve() = default;
2✔
138

139
void pdns::RecResolve::stopRefresher()
140
{
×
141
  d_refresher.finish();
×
142
}
×
143

144
void pdns::RecResolve::startRefresher()
145
{
×
146
  d_refresher.start();
×
147
}
×
148

149
ComboAddress pdns::RecResolve::lookupAndRegister(const std::string& name, time_t now)
150
{
2✔
151
  if (s_ttl == 0) {
2!
152
    throw PDNSException("config tried to resolve `" + name + "' but system resolver feature not enabled");
×
153
  }
×
154
  auto data = d_data.lock();
2✔
155
  if (auto iter = data->d_map.find(name); iter != data->d_map.end()) {
2!
UNCOV
156
    if (iter->second.d_ttd < now) {
×
157
      return iter->second.d_address;
×
158
    }
×
159
    // If it's stale, re-resolve below
UNCOV
160
  }
×
161
  // We keep the lock while resolving, even though this might take a while...
162
  auto address = resolve(name);
2✔
163

164
  time_t ttd = now + d_ttl;
2✔
165
  auto iter = data->d_map.emplace(name, AddressData{address, ttd}).first;
2✔
166
  return iter->second.d_address;
2✔
167
}
2✔
168

169
ComboAddress pdns::RecResolve::lookup(const std::string& name)
170
{
4✔
171
  auto data = d_data.lock();
4✔
172
  if (auto iter = data->d_map.find(name); iter != data->d_map.end()) {
4✔
173
    // always return it, even if it's stale
174
    return iter->second.d_address;
2✔
175
  }
2✔
176
  throw PDNSException("system resolve of unregistered name: " + name);
2✔
177
}
4✔
178

179
void pdns::RecResolve::wipe(const string& name)
180
{
2✔
181
  auto data = d_data.lock();
2✔
182
  if (name.empty()) {
2!
183
    data->d_map.clear();
×
184
  }
×
185
  else {
2✔
186
    data->d_map.erase(name);
2✔
187
  }
2✔
188
}
2✔
189

190
bool pdns::RecResolve::refresh(time_t now)
191
{
2✔
192
  // The refresh task should not take the lock for a long time, so we're working on a copy
193
  ResolveData copy;
2✔
194
  {
2✔
195
    auto data = d_data.lock();
2✔
196
    copy = *data;
2✔
197
  }
2✔
198
  std::map<std::string, AddressData> newData;
2✔
199

200
  auto log = g_slog->withName("system-resolver");
2✔
201

202
  bool updated = false;
2✔
203
  for (const auto& entry : copy.d_map) {
2!
204
    if (entry.second.d_ttd <= now) {
×
205
      auto newAddress = resolve(entry.first);
×
206
      time_t ttd = now;
×
207
      if (newAddress != ComboAddress()) {
×
208
        // positive resolve, good for ttl
209
        ttd += d_ttl;
×
210
      }
×
211
      else {
×
212
        log->error(Logr::Error, "Name did not resolve", "name", Logging::Loggable(entry.first));
×
213
      }
×
214
      if (newAddress != entry.second.d_address) {
×
215
        log->info(Logr::Debug, "Name resolved to new address",
×
216
                  "name", Logging::Loggable(entry.first),
×
217
                  "address", Logging::Loggable(newAddress.toString()));
×
218
        // An address changed
219
        updated = true;
×
220
      }
×
221
      newData.emplace(entry.first, AddressData{newAddress, ttd});
×
222
    }
×
223
  }
×
224

225
  if (!newData.empty()) {
2!
226
    auto data = d_data.lock();
×
227
    for (const auto& entry : newData) {
×
228
      data->d_map.insert_or_assign(entry.first, entry.second);
×
229
    }
×
230
  }
×
231
  if (updated) {
2!
232
    log->info(Logr::Info, "Changes in names detected");
×
233
  }
×
234
  return updated;
2✔
235
}
2✔
236

237
pdns::RecResolve::Refresher::Refresher(time_t interval, const std::function<void()>& callback, bool selfResolveCheck, pdns::RecResolve& res) :
238
  d_resolver(res), d_callback(callback), d_interval(std::max(static_cast<time_t>(1), interval)), d_selfResolveCheck(selfResolveCheck)
239
{
2✔
240
  start();
2✔
241
}
2✔
242

243
pdns::RecResolve::Refresher::~Refresher()
244
{
2✔
245
  finish();
2✔
246
}
2✔
247

248
void pdns::RecResolve::Refresher::refreshLoop()
249
{
2✔
250
  setThreadName("rec/sysres");
2✔
251
  time_t lastSelfCheck = 0;
2✔
252

253
  while (!stop) {
6✔
254
    const time_t startTime = time(nullptr);
4✔
255
    time_t wakeTime = startTime;
4✔
256
    // The expresion wakeTime - startTime is equal to the total amount of time slept
257
    while (wakeTime - startTime < d_interval) {
6✔
258
      std::unique_lock lock(mutex);
4✔
259
      time_t remaining = d_interval - (wakeTime - startTime);
4✔
260
      if (remaining <= 0) {
4!
261
        break;
×
262
      }
×
263
      condVar.wait_for(lock, std::chrono::seconds(remaining),
4✔
264
                       [&doWakeup = wakeup] { return doWakeup.load(); });
8✔
265
      wakeup = false;
4✔
266
      if (stop) {
4✔
267
        break;
2✔
268
      }
2✔
269
      if (d_selfResolveCheck && lastSelfCheck < time(nullptr) - 3600) {
2!
270
        lastSelfCheck = time(nullptr);
×
271
        auto resolvedServerID = serverID();
×
272
        if (resolvedServerID == s_serverID) {
×
273
          auto log = g_slog->withName("system-resolver");
×
274
          log->info(Logr::Error, "id.server/CH/TXT resolves to my own server identity", "id.server", Logging::Loggable(resolvedServerID));
×
275
        }
×
276
      }
×
277
      bool changes = d_resolver.refresh(time(nullptr));
2✔
278
      wakeTime = time(nullptr);
2✔
279
      if (changes) {
2!
280
        d_callback();
×
281
      }
×
282
    }
2✔
283
  }
4✔
284
}
2✔
285

286
void pdns::RecResolve::Refresher::finish()
287
{
2✔
288
  stop = true;
2✔
289
  wakeup = true;
2✔
290
  condVar.notify_one();
2✔
291
  d_thread.join();
2✔
292
}
2✔
293

294
void pdns::RecResolve::Refresher::start()
295
{
2✔
296
  stop = false;
2✔
297
  wakeup = false;
2✔
298
  d_thread = std::thread([this]() { refreshLoop(); });
2✔
299
}
2✔
300

301
void pdns::RecResolve::Refresher::trigger()
302
{
×
303
  stop = true;
×
304
  wakeup = true;
×
305
  condVar.notify_one();
×
306
}
×
307

308
ComboAddress pdns::fromNameOrIP(const string& str, uint16_t defPort, Logr::log_t log)
UNCOV
309
{
×
UNCOV
310
  try {
×
UNCOV
311
    ComboAddress addr = parseIPAndPort(str, defPort);
×
UNCOV
312
    return addr;
×
UNCOV
313
  }
×
UNCOV
314
  catch (const PDNSException&) {
×
UNCOV
315
    uint16_t port = defPort;
×
UNCOV
316
    string::size_type pos = str.rfind(':');
×
UNCOV
317
    if (pos != string::npos) {
×
UNCOV
318
      port = pdns::checked_stoi<uint16_t>(str.substr(pos + 1));
×
UNCOV
319
    }
×
UNCOV
320
    auto& res = pdns::RecResolve::getInstance();
×
UNCOV
321
    ComboAddress address = res.lookupAndRegister(str.substr(0, pos), time(nullptr));
×
UNCOV
322
    if (address != ComboAddress()) {
×
UNCOV
323
      address.setPort(port);
×
UNCOV
324
      return address;
×
UNCOV
325
    }
×
326
    log->error(Logr::Error, "Could not resolve name", "name", Logging::Loggable(str));
×
327
    throw PDNSException("Could not resolve " + str);
×
UNCOV
328
  }
×
UNCOV
329
}
×
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