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

PowerDNS / pdns / 12595591960

03 Jan 2025 09:27AM UTC coverage: 62.774% (+2.5%) from 60.245%
12595591960

Pull #15008

github

web-flow
Merge c2a2749d3 into 788f396a7
Pull Request #15008: Do not follow CNAME records for ANY or CNAME queries

30393 of 78644 branches covered (38.65%)

Branch coverage included in aggregate %.

105822 of 138350 relevant lines covered (76.49%)

4613078.44 hits per line

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

88.26
/pdns/recursordist/reczones-helpers.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
#ifdef HAVE_CONFIG_H
24
#include "config.h"
25
#endif
26

27
#include "arguments.hh"
28
#include "syncres.hh"
29
#include "reczones-helpers.hh"
30
#include "root-addresses.hh"
31
#include "zoneparser-tng.hh"
32

33
static void putIntoCache(time_t now, QType qtype, vState state, const ComboAddress& from, const set<DNSName>& seen, const std::multimap<DNSName, DNSRecord>& allRecords)
34
{
560✔
35
  for (const auto& name : seen) {
562✔
36
    auto records = allRecords.equal_range(name);
562✔
37
    vector<DNSRecord> aset;
562✔
38
    for (auto elem = records.first; elem != records.second; ++elem) {
1,130✔
39
      aset.emplace_back(elem->second);
568✔
40
    }
568✔
41
    // Put non-default root hints into cache as authoritative.  As argued below in
42
    // putDefaultHintsIntoCache, this is actually wrong, but people might depend on it by having
43
    // root-hints that refer to servers that aren't actually capable or willing to serve root data.
44
    g_recCache->replace(now, name, qtype, aset, {}, {}, true, g_rootdnsname, boost::none, boost::none, state, from);
562✔
45
  }
562✔
46
}
560✔
47

48
static void parseHintFile(time_t now, const std::string& hintfile, set<DNSName>& seenA, set<DNSName>& seenAAAA, set<DNSName>& seenNS, std::multimap<DNSName, DNSRecord>& aRecords, std::multimap<DNSName, DNSRecord>& aaaaRecords, vector<DNSRecord>& nsvec)
49
{
280✔
50
  ZoneParserTNG zpt(hintfile);
280✔
51
  zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
280✔
52
  zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
280✔
53
  DNSResourceRecord rrecord;
280✔
54

55
  while (zpt.get(rrecord)) {
1,130✔
56
    rrecord.ttl += now;
850✔
57
    switch (rrecord.qtype) {
850!
58
    case QType::A:
286✔
59
      seenA.insert(rrecord.qname);
286✔
60
      aRecords.emplace(rrecord.qname, DNSRecord(rrecord));
286✔
61
      break;
286✔
62
    case QType::AAAA:
282✔
63
      seenAAAA.insert(rrecord.qname);
282✔
64
      aaaaRecords.emplace(rrecord.qname, DNSRecord(rrecord));
282✔
65
      break;
282✔
66
    case QType::NS:
282✔
67
      seenNS.emplace(rrecord.content);
282✔
68
      rrecord.content = toLower(rrecord.content);
282✔
69
      nsvec.emplace_back(rrecord);
282✔
70
      break;
282✔
71
    }
850✔
72
  }
850✔
73
}
280✔
74

75
static bool determineReachable(const set<DNSName>& names, const set<DNSName>& nameservers)
76
{
560✔
77
  bool reachable = false;
560✔
78
  for (auto const& record : names) {
560!
79
    if (nameservers.count(record) != 0) {
560!
80
      reachable = true;
560✔
81
      break;
560✔
82
    }
560✔
83
  }
560✔
84
  return reachable;
560✔
85
}
560✔
86

87
bool readHintsIntoCache(time_t now, const std::string& hintfile, std::vector<DNSRecord>& nsvec)
88
{
280✔
89
  const ComboAddress from("255.255.255.255");
280✔
90
  set<DNSName> seenNS;
280✔
91
  set<DNSName> seenA;
280✔
92
  set<DNSName> seenAAAA;
280✔
93

94
  std::multimap<DNSName, DNSRecord> aRecords;
280✔
95
  std::multimap<DNSName, DNSRecord> aaaaRecords;
280✔
96

97
  parseHintFile(now, hintfile, seenA, seenAAAA, seenNS, aRecords, aaaaRecords, nsvec);
280✔
98

99
  putIntoCache(now, QType::A, vState::Insecure, from, seenA, aRecords);
280✔
100
  putIntoCache(now, QType::AAAA, vState::Insecure, from, seenAAAA, aaaaRecords);
280✔
101

102
  bool reachableA = determineReachable(seenA, seenNS);
280✔
103
  bool reachableAAAA = determineReachable(seenAAAA, seenNS);
280✔
104

105
  auto log = g_slog->withName("config");
280✔
106
  if (SyncRes::s_doIPv4 && !SyncRes::s_doIPv6 && !reachableA) {
280!
107
    SLOG(g_log << Logger::Error << "Running IPv4 only but no IPv4 root hints" << endl,
×
108
         log->info(Logr::Error, "Running IPv4 only but no IPv4 root hints"));
×
109
    return false;
×
110
  }
×
111
  if (!SyncRes::s_doIPv4 && SyncRes::s_doIPv6 && !reachableAAAA) {
280!
112
    SLOG(g_log << Logger::Error << "Running IPv6 only but no IPv6 root hints" << endl,
×
113
         log->info(Logr::Error, "Running IPv6 only but no IPv6 root hints"));
×
114
    return false;
×
115
  }
×
116
  if (SyncRes::s_doIPv4 && SyncRes::s_doIPv6 && !reachableA && !reachableAAAA) {
280!
117
    SLOG(g_log << Logger::Error << "No valid root hints" << endl,
×
118
         log->info(Logr::Error, "No valid root hints"));
×
119
    return false;
×
120
  }
×
121
  return true;
280✔
122
}
280✔
123

124
void putDefaultHintsIntoCache(time_t now, std::vector<DNSRecord>& nsvec)
125
{
22✔
126
  const ComboAddress from("255.255.255.255");
22✔
127

128
  DNSRecord arr;
22✔
129
  DNSRecord aaaarr;
22✔
130
  DNSRecord nsrr;
22✔
131

132
  nsrr.d_name = g_rootdnsname;
22✔
133
  arr.d_type = QType::A;
22✔
134
  aaaarr.d_type = QType::AAAA;
22✔
135
  nsrr.d_type = QType::NS;
22✔
136
  // coverity[store_truncates_time_t]
137
  arr.d_ttl = aaaarr.d_ttl = nsrr.d_ttl = now + 3600000;
22✔
138

139
  string templ = "a.root-servers.net.";
22✔
140

141
  static_assert(rootIps4.size() == rootIps6.size());
22✔
142

143
  for (size_t letter = 0; letter < rootIps4.size(); ++letter) {
308✔
144
    templ.at(0) = static_cast<char>(letter + 'a');
286✔
145
    aaaarr.d_name = arr.d_name = DNSName(templ);
286✔
146
    nsrr.setContent(std::make_shared<NSRecordContent>(DNSName(templ)));
286✔
147
    nsvec.push_back(nsrr);
286✔
148

149
    if (!rootIps4.at(letter).empty()) {
286!
150
      arr.setContent(std::make_shared<ARecordContent>(ComboAddress(rootIps4.at(letter))));
286✔
151
      /*
152
       * Originally the hint records were inserted with the auth flag set, with the consequence that
153
       * data from AUTHORITY and ADDITIONAL sections (as seen in a . NS response) were not used. This
154
       * (together with the long ttl) caused outdated hint to be kept in cache. So insert as non-auth,
155
       * and the extra sections in the . NS refreshing cause the cached records to be updated with
156
       * up-to-date information received from a real root server.
157
       *
158
       * Note that if a user query is done for one of the root-server.net names, it will be inserted
159
       * into the cache with the auth bit set. Further NS refreshes will not update that entry. If all
160
       * root names are queried at the same time by a user, all root-server.net names will be marked
161
       * auth and will expire at the same time. A re-prime is then triggered, as before, when the
162
       * records were inserted with the auth bit set and the TTD comes.
163
       */
164
      g_recCache->replace(now, DNSName(templ), QType::A, {arr}, {}, {}, false, g_rootdnsname, boost::none, boost::none, vState::Insecure, from);
286✔
165
    }
286✔
166
    if (!rootIps6.at(letter).empty()) {
286!
167
      aaaarr.setContent(std::make_shared<AAAARecordContent>(ComboAddress(rootIps6.at(letter))));
286✔
168
      g_recCache->replace(now, DNSName(templ), QType::AAAA, {aaaarr}, {}, {}, false, g_rootdnsname, boost::none, boost::none, vState::Insecure, from);
286✔
169
    }
286✔
170
  }
286✔
171
}
22✔
172

173
template <typename T>
174
static SyncRes::AuthDomain makeSOAAndNSNodes(DNSRecord& dr, T content)
175
{
5,651✔
176
  dr.d_class = 1;
5,651✔
177
  dr.d_place = DNSResourceRecord::ANSWER;
5,651✔
178
  dr.d_ttl = 86400;
5,651✔
179
  dr.d_type = QType::SOA;
5,651✔
180
  dr.setContent(DNSRecordContent::make(QType::SOA, 1, "localhost. root 1 604800 86400 2419200 604800"));
5,651✔
181

182
  SyncRes::AuthDomain ad;
5,651✔
183
  ad.d_rdForward = false;
5,651✔
184
  ad.d_records.insert(dr);
5,651✔
185

186
  dr.d_type = QType::NS;
5,651✔
187
  dr.setContent(std::make_shared<NSRecordContent>(content));
5,651✔
188
  ad.d_records.insert(dr);
5,651✔
189

190
  return ad;
5,651✔
191
}
5,651✔
192

193
static void addToDomainMap(SyncRes::domainmap_t& newMap,
194
                           SyncRes::AuthDomain ad,
195
                           const DNSName& name,
196
                           Logr::log_t log,
197
                           const bool partial = false,
198
                           const bool reverse = false)
199
{
5,595✔
200
  if (newMap.count(name) != 0) {
5,595✔
201
    SLOG(g_log << Logger::Warning << "Will not overwrite zone '" << name << "' already loaded" << endl,
8✔
202
         log->info(Logr::Warning, "Will not overwrite already loaded zone", "zone",
8✔
203
                   Logging::Loggable(name)));
8✔
204
  }
8✔
205
  else {
5,587✔
206
    if (!partial) {
5,587✔
207
      const auto direction = reverse ? std::string{"reverse"} : std::string{"forward"};
32!
208
      SLOG(g_log << Logger::Warning << "Inserting " << direction << " zone '" << name << "' based on hosts file" << endl,
32✔
209
           log->info(Logr::Notice, "Inserting " + direction + " zone based on hosts file", "zone", Logging::Loggable(name)));
32✔
210
    }
32✔
211
    ad.d_name = name;
5,587✔
212
    newMap[ad.d_name] = ad;
5,587✔
213
  }
5,587✔
214
}
5,595✔
215

216
static void makeNameToIPZone(SyncRes::domainmap_t& newMap,
217
                             const DNSName& hostname,
218
                             const ComboAddress& address)
219
{
60✔
220
  DNSRecord dr;
60✔
221
  dr.d_name = hostname;
60✔
222

223
  auto entry = newMap.find(hostname);
60✔
224
  if (entry == newMap.end()) {
60✔
225
    auto ad = makeSOAAndNSNodes(dr, "localhost.");
56✔
226
    ad.d_name = dr.d_name;
56✔
227
    entry = newMap.insert({dr.d_name, ad}).first;
56✔
228
  }
56✔
229

230
  auto recType = address.isIPv6() ? QType::AAAA : QType::A;
60✔
231
  dr.d_type = recType;
60✔
232
  dr.d_ttl = 86400;
60✔
233
  dr.setContent(DNSRecordContent::make(recType, QClass::IN, address.toStringNoInterface()));
60✔
234
  entry->second.d_records.insert(dr);
60✔
235
}
60✔
236

237
static void makeIPToNamesZone(SyncRes::domainmap_t& newMap,
238
                              const ComboAddress& address,
239
                              const std::string& canonicalHostname,
240
                              Logr::log_t log)
241
{
40✔
242
  DNSRecord dr;
40✔
243
  dr.d_name = DNSName(address.toStringReversed());
40✔
244
  dr.d_name.appendRawLabel(address.isIPv4() ? "in-addr" : "ip6");
40✔
245
  dr.d_name.appendRawLabel("arpa");
40✔
246

247
  SyncRes::AuthDomain ad = makeSOAAndNSNodes(dr, DNSName("localhost."));
40✔
248

249
  // Add a PTR entry for the primary name for reverse lookups.
250
  dr.d_type = QType::PTR;
40✔
251
  dr.setContent(DNSRecordContent::make(QType::PTR, 1, DNSName(canonicalHostname).toString()));
40✔
252
  ad.d_records.insert(dr);
40✔
253

254
  addToDomainMap(newMap, std::move(ad), dr.d_name, log, false, true);
40✔
255
}
40✔
256

257
void makePartialIPZone(SyncRes::domainmap_t& newMap,
258
                       std::initializer_list<const char*> labels,
259
                       Logr::log_t log)
260
{
4,344✔
261
  DNSRecord dr;
4,344✔
262
  for (auto label = std::rbegin(labels); label != std::rend(labels); ++label) {
13,376✔
263
    dr.d_name.appendRawLabel(*label);
9,032✔
264
  }
9,032✔
265
  dr.d_name.appendRawLabel("in-addr");
4,344✔
266
  dr.d_name.appendRawLabel("arpa");
4,344✔
267

268
  SyncRes::AuthDomain ad = makeSOAAndNSNodes(dr, DNSName("localhost."));
4,344✔
269

270
  addToDomainMap(newMap, std::move(ad), dr.d_name, log, true, true);
4,344✔
271
}
4,344✔
272

273
void makePartialIP6Zone(SyncRes::domainmap_t& newMap,
274
                        const std::string& name,
275
                        Logr::log_t log)
276
{
1,211✔
277
  DNSRecord dnsRecord;
1,211✔
278
  dnsRecord.d_name = DNSName(name);
1,211✔
279
  SyncRes::AuthDomain authDomain = makeSOAAndNSNodes(dnsRecord, DNSName("localhost."));
1,211✔
280

281
  addToDomainMap(newMap, std::move(authDomain), dnsRecord.d_name, log, true, true);
1,211✔
282
}
1,211✔
283

284
void addForwardAndReverseLookupEntries(SyncRes::domainmap_t& newMap,
285
                                       const std::string& searchSuffix,
286
                                       const std::vector<std::string>& parts,
287
                                       Logr::log_t log)
288
{
40✔
289
  const ComboAddress address{parts[0]};
40✔
290

291
  // Go over the hostname and aliases (parts[1], parts[2], etc...) and add entries
292
  // for forward lookups.
293
  for (auto name = parts.cbegin() + 1; name != parts.cend(); ++name) {
100✔
294
    if (searchSuffix.empty() || name->find('.') != string::npos) {
60✔
295
      makeNameToIPZone(newMap, DNSName(*name), address);
36✔
296
    }
36✔
297
    else {
24✔
298
      DNSName canonical = toCanonic(DNSName(searchSuffix), *name);
24✔
299
      if (canonical != DNSName(*name)) {
24!
300
        makeNameToIPZone(newMap, canonical, address);
24✔
301
      }
24✔
302
    }
24✔
303
  }
60✔
304

305
  // Add entries for the primary name for reverse lookups.
306
  if (searchSuffix.empty() || parts[1].find('.') != string::npos) {
40✔
307
    makeIPToNamesZone(newMap, address, parts[1], log);
24✔
308
  }
24✔
309
  else {
16✔
310
    DNSName canonical = toCanonic(DNSName(searchSuffix), parts[1]);
16✔
311
    makeIPToNamesZone(newMap, address, canonical.toString(), log);
16✔
312
  }
16✔
313
}
40✔
314

315
bool parseEtcHostsLine(std::vector<std::string>& parts, std::string& line)
316
{
20✔
317
  const string::size_type pos = line.find('#');
20✔
318
  if (pos != string::npos) {
20!
319
    line.resize(pos);
×
320
  }
×
321
  boost::trim(line);
20✔
322
  if (line.empty()) {
20!
323
    return false;
×
324
  }
×
325
  parts.clear();
20✔
326
  stringtok(parts, line, "\t\r\n ");
20✔
327
  return parts.size() >= 2;
20✔
328
}
20✔
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