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

PowerDNS / pdns / 18554180908

16 Oct 2025 07:48AM UTC coverage: 65.813% (-0.01%) from 65.825%
18554180908

Pull #16271

github

web-flow
Merge 4dcb65194 into feeb24672
Pull Request #16271: auth-5.0.x: Backport 15267: Fix the build-packages workflow

42046 of 92452 branches covered (45.48%)

Branch coverage included in aggregate %.

127953 of 165855 relevant lines covered (77.15%)

5719692.12 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
{
18✔
41
  struct addrinfo hints = {};
18✔
42
  hints.ai_flags = AI_ADDRCONFIG;
18✔
43
  hints.ai_family = 0;
18✔
44

45
  struct addrinfo* res0 = nullptr;
18✔
46
  auto ret = getaddrinfo(name.c_str(), nullptr, &hints, &res0);
18✔
47
  // We pick the first address after sorting for now, no handling of multiple addresses or AF selection.
48
  vector<ComboAddress> vec;
18✔
49
  if (ret != 0) {
18!
50
    return {};
×
51
  }
×
52
  auto* res = res0;
18✔
53
  while (res != nullptr) {
126✔
54
    try {
108✔
55
      auto address = ComboAddress{res->ai_addr, res->ai_addrlen};
108✔
56
      vec.emplace_back(address);
108✔
57
    }
108✔
58
    catch (...) {
108✔
59
    }
×
60
    res = res->ai_next;
108✔
61
  }
108✔
62
  freeaddrinfo(res0);
18✔
63
  if (!vec.empty()) {
18!
64
    std::sort(vec.begin(), vec.end());
18✔
65
    return vec.at(0);
18✔
66
  }
18✔
67
  return {};
×
68
}
18✔
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
{
4✔
119
  pdns::RecResolve::s_serverID = std::move(serverID);
4✔
120
  pdns::RecResolve::s_ttl = ttl;
4✔
121
  pdns::RecResolve::s_interval = interval;
4✔
122
  pdns::RecResolve::s_selfResolveCheck = selfResolveCheck;
4✔
123
  pdns::RecResolve::s_callback = callback;
4✔
124
}
4✔
125

126
pdns::RecResolve& pdns::RecResolve::getInstance()
127
{
18✔
128
  static unique_ptr<RecResolve> res = make_unique<pdns::RecResolve>(s_ttl, s_interval, s_selfResolveCheck, s_callback);
18✔
129
  return *res;
18✔
130
}
18✔
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
{
4✔
135
}
4✔
136

137
pdns::RecResolve::~RecResolve() = default;
4✔
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
{
18✔
151
  if (s_ttl == 0) {
18!
152
    throw PDNSException("config tried to resolve `" + name + "' but system resolver feature not enabled");
×
153
  }
×
154
  auto data = d_data.lock();
18✔
155
  if (auto iter = data->d_map.find(name); iter != data->d_map.end()) {
18✔
156
    if (iter->second.d_ttd < now) {
12!
157
      return iter->second.d_address;
×
158
    }
×
159
    // If it's stale, re-resolve below
160
  }
12✔
161
  // We keep the lock while resolving, even though this might take a while...
162
  auto address = resolve(name);
18✔
163

164
  time_t ttd = now + d_ttl;
18✔
165
  auto iter = data->d_map.emplace(name, AddressData{address, ttd}).first;
18✔
166
  return iter->second.d_address;
18✔
167
}
18✔
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
{
×
192
  // The refresh task should not take the lock for a long time, so we're working on a copy
193
  ResolveData copy;
×
194
  {
×
195
    auto data = d_data.lock();
×
196
    copy = *data;
×
197
  }
×
198
  std::map<std::string, AddressData> newData;
×
199

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

202
  bool updated = false;
×
203
  for (const auto& entry : copy.d_map) {
×
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()) {
×
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) {
×
232
    log->info(Logr::Info, "Changes in names detected");
×
233
  }
×
234
  return updated;
×
235
}
×
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
{
4✔
240
  start();
4✔
241
}
4✔
242

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

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

253
  while (!stop) {
8✔
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) {
4!
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;
4✔
268
      }
4✔
269
      if (d_selfResolveCheck && lastSelfCheck < time(nullptr) - 3600) {
×
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));
×
278
      wakeTime = time(nullptr);
×
279
      if (changes) {
×
280
        d_callback();
×
281
      }
×
282
    }
×
283
  }
4✔
284
}
4✔
285

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

294
void pdns::RecResolve::Refresher::start()
295
{
4✔
296
  stop = false;
4✔
297
  wakeup = false;
4✔
298
  d_thread = std::thread([this]() { refreshLoop(); });
4✔
299
}
4✔
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)
309
{
2,860✔
310
  try {
2,860✔
311
    ComboAddress addr = parseIPAndPort(str, defPort);
2,860✔
312
    return addr;
2,860✔
313
  }
2,860✔
314
  catch (const PDNSException&) {
2,860✔
315
    uint16_t port = defPort;
16✔
316
    string::size_type pos = str.rfind(':');
16✔
317
    if (pos != string::npos) {
16✔
318
      port = pdns::checked_stoi<uint16_t>(str.substr(pos + 1));
13✔
319
    }
13✔
320
    auto& res = pdns::RecResolve::getInstance();
16✔
321
    ComboAddress address = res.lookupAndRegister(str.substr(0, pos), time(nullptr));
16✔
322
    if (address != ComboAddress()) {
16!
323
      address.setPort(port);
16✔
324
      return address;
16✔
325
    }
16✔
326
    log->error(Logr::Error, "Could not resolve name", "name", Logging::Loggable(str));
×
327
    throw PDNSException("Could not resolve " + str);
×
328
  }
16✔
329
}
2,860✔
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