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

PowerDNS / pdns / 18743945403

23 Oct 2025 09:29AM UTC coverage: 65.845% (+0.02%) from 65.829%
18743945403

Pull #16356

github

web-flow
Merge 8a2027ef1 into efa3637e8
Pull Request #16356: auth 5.0: backport "pdnsutil: fix b2b-migrate to from sql to non-sql"

42073 of 92452 branches covered (45.51%)

Branch coverage included in aggregate %.

128008 of 165855 relevant lines covered (77.18%)

6379935.17 hits per line

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

84.02
/pdns/recursordist/filterpo.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 <cinttypes>
24
#include <iostream>
25
#include <boost/format.hpp>
26

27
#include "filterpo.hh"
28
#include "namespaces.hh"
29
#include "dnsrecords.hh"
30

31
// Names below are RPZ Actions and end with a dot (except "Local Data")
32
static const std::string rpzDropName("rpz-drop."),
33
  rpzTruncateName("rpz-tcp-only."),
34
  rpzNoActionName("rpz-passthru."),
35
  rpzCustomName("Local Data");
36

37
// Names below are (part) of RPZ Trigger names and do NOT end with a dot
38
static const std::string rpzClientIPName("rpz-client-ip"),
39
  rpzIPName("rpz-ip"),
40
  rpzNSDnameName("rpz-nsdname"),
41
  rpzNSIPName("rpz-nsip");
42

43
DNSFilterEngine::DNSFilterEngine() = default;
1,181✔
44

45
bool DNSFilterEngine::Zone::findExactQNamePolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const
46
{
722✔
47
  return findExactNamedPolicy(d_qpolName, qname, pol);
722✔
48
}
722✔
49

50
bool DNSFilterEngine::Zone::findExactNSPolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const
51
{
150✔
52
  if (findExactNamedPolicy(d_propolName, qname, pol)) {
150✔
53
    // hitdata set by findExactNamedPolicy
54
    pol.d_hitdata->d_trigger.appendRawLabel(rpzNSDnameName);
12✔
55
    return true;
12✔
56
  }
12✔
57
  return false;
138✔
58
}
150✔
59

60
bool DNSFilterEngine::Zone::findNSIPPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
61
{
2,517✔
62
  if (const auto* fnd = d_propolNSAddr.lookup(addr)) {
2,517✔
63
    pol = fnd->second;
13✔
64
    pol.setHitData(Zone::maskToRPZ(fnd->first), addr.toString());
13✔
65
    pol.d_hitdata->d_trigger.appendRawLabel(rpzNSIPName);
13✔
66
    return true;
13✔
67
  }
13✔
68
  return false;
2,504✔
69
}
2,517✔
70

71
bool DNSFilterEngine::Zone::findResponsePolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
72
{
654✔
73
  if (const auto* fnd = d_postpolAddr.lookup(addr)) {
654✔
74
    pol = fnd->second;
15✔
75
    pol.setHitData(Zone::maskToRPZ(fnd->first), addr.toString());
15✔
76
    pol.d_hitdata->d_trigger.appendRawLabel(rpzIPName);
15✔
77
    return true;
15✔
78
  }
15✔
79
  return false;
639✔
80
}
654✔
81

82
bool DNSFilterEngine::Zone::findClientPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
83
{
271✔
84
  if (const auto* fnd = d_qpolAddr.lookup(addr)) {
271✔
85
    pol = fnd->second;
16✔
86
    pol.setHitData(Zone::maskToRPZ(fnd->first), addr.toString());
16✔
87
    pol.d_hitdata->d_trigger.appendRawLabel(rpzClientIPName);
16✔
88
    return true;
16✔
89
  }
16✔
90
  return false;
255✔
91
}
271✔
92

93
bool DNSFilterEngine::Zone::findNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol)
94
{
×
95
  if (polmap.empty()) {
×
96
    return false;
×
97
  }
×
98

99
  /* for www.powerdns.com, we need to check:
100
     www.powerdns.com.
101
       *.powerdns.com.
102
                *.com.
103
                    *.
104
   */
105

106
  std::unordered_map<DNSName, DNSFilterEngine::Policy>::const_iterator iter;
×
107
  iter = polmap.find(qname);
×
108

109
  if (iter != polmap.end()) {
×
110
    pol = iter->second;
×
111
    return true;
×
112
  }
×
113

114
  DNSName sub(qname);
×
115
  while (sub.chopOff()) {
×
116
    iter = polmap.find(g_wildcarddnsname + sub);
×
117
    if (iter != polmap.end()) {
×
118
      pol = iter->second;
×
119
      pol.setHitData(iter->first, qname.toStringNoDot());
×
120
      return true;
×
121
    }
×
122
  }
×
123
  return false;
×
124
}
×
125

126
bool DNSFilterEngine::Zone::findExactNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol)
127
{
872✔
128
  if (polmap.empty()) {
872!
129
    return false;
×
130
  }
×
131

132
  const auto iter = polmap.find(qname);
872✔
133
  if (iter != polmap.end()) {
872✔
134
    pol = iter->second;
152✔
135
    pol.setHitData(qname, qname.toStringNoDot());
152✔
136
    return true;
152✔
137
  }
152✔
138

139
  return false;
720✔
140
}
872✔
141

142
bool DNSFilterEngine::getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
143
{
77,898✔
144
  // cout<<"Got question for nameserver name "<<qname<<endl;
145
  std::vector<bool> zoneEnabled(d_zones.size());
77,898✔
146
  size_t count = 0;
77,898✔
147
  bool allEmpty = true;
77,898✔
148
  for (const auto& zone : d_zones) {
77,898✔
149
    bool enabled = true;
9,046✔
150
    const auto& zoneName = zone->getName();
9,046✔
151
    if (zone->getPriority() >= pol.getPriority() || discardedPolicies.find(zoneName) != discardedPolicies.end()) {
9,046✔
152
      enabled = false;
25✔
153
    }
25✔
154
    else {
9,021✔
155
      if (zone->hasNSPolicies()) {
9,021✔
156
        allEmpty = false;
42✔
157
      }
42✔
158
      else {
8,979✔
159
        enabled = false;
8,979✔
160
      }
8,979✔
161
    }
9,021✔
162

163
    zoneEnabled[count] = enabled;
9,046✔
164
    ++count;
9,046✔
165
  }
9,046✔
166

167
  if (allEmpty) {
77,899✔
168
    return false;
77,853✔
169
  }
77,853✔
170

171
  /* prepare the wildcard-based names */
172
  std::vector<DNSName> wcNames;
2,147,483,687✔
173
  wcNames.reserve(qname.countLabels());
2,147,483,687✔
174
  DNSName sub(qname);
2,147,483,687✔
175
  while (sub.chopOff()) {
2,147,483,809✔
176
    wcNames.emplace_back(g_wildcarddnsname + sub);
122✔
177
  }
122✔
178

179
  count = 0;
2,147,483,687✔
180
  for (const auto& zone : d_zones) {
2,147,483,687✔
181
    if (!zoneEnabled[count]) {
40!
182
      ++count;
×
183
      continue;
×
184
    }
×
185
    if (zone->findExactNSPolicy(qname, pol)) {
40✔
186
      // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
187
      return true;
6✔
188
    }
6✔
189

190
    for (const auto& wildcard : wcNames) {
100✔
191
      if (zone->findExactNSPolicy(wildcard, pol)) {
100✔
192
        // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
193
        // Hit is not the wildcard passed to findExactQNamePolicy but the actual qname!
194
        pol.d_hitdata->d_hit = qname.toStringNoDot();
2✔
195
        return true;
2✔
196
      }
2✔
197
    }
100✔
198
    ++count;
32✔
199
  }
32✔
200

201
  return false;
2,147,483,679✔
202
}
2,147,483,687✔
203

204
bool DNSFilterEngine::getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
205
{
14,428✔
206
  //  cout<<"Got question for nameserver IP "<<address.toString()<<endl;
207
  for (const auto& zone : d_zones) {
14,428✔
208
    if (zone->getPriority() >= pol.getPriority()) {
2,520✔
209
      break;
5✔
210
    }
5✔
211
    const auto& zoneName = zone->getName();
2,515✔
212
    if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
2,515✔
213
      continue;
2✔
214
    }
2✔
215

216
    if (zone->findNSIPPolicy(address, pol)) {
2,513✔
217
      //      cerr<<"Had a hit on the nameserver ("<<address.toString()<<") used to process the query"<<endl;
218
      return true;
11✔
219
    }
11✔
220
  }
2,513✔
221
  return false;
14,417✔
222
}
14,428✔
223

224
bool DNSFilterEngine::getClientPolicy(const ComboAddress& address, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
225
{
3,150✔
226
  // cout<<"Got question from "<<ca.toString()<<endl;
227
  for (const auto& zone : d_zones) {
3,152✔
228
    if (zone->getPriority() >= pol.getPriority()) {
275✔
229
      break;
2✔
230
    }
2✔
231
    const auto& zoneName = zone->getName();
273✔
232
    if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
273✔
233
      continue;
6✔
234
    }
6✔
235

236
    if (zone->findClientPolicy(address, pol)) {
267✔
237
      // cerr<<"Had a hit on the IP address ("<<ca.toString()<<") of the client"<<endl;
238
      return true;
14✔
239
    }
14✔
240
  }
267✔
241
  return false;
3,136✔
242
}
3,150✔
243

244
bool DNSFilterEngine::getQueryPolicy(const DNSName& qname, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
245
{
22,929✔
246
  // cerr<<"Got question for "<<qname<<' '<< pol.getPriority()<< endl;
247
  std::vector<bool> zoneEnabled(d_zones.size());
22,929✔
248
  size_t count = 0;
22,929✔
249
  bool allEmpty = true;
22,929✔
250
  for (const auto& zone : d_zones) {
22,929✔
251
    bool enabled = true;
6,147✔
252
    if (zone->getPriority() >= pol.getPriority()) {
6,147✔
253
      enabled = false;
14✔
254
    }
14✔
255
    else {
6,133✔
256
      const auto& zoneName = zone->getName();
6,133✔
257
      if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
6,133✔
258
        enabled = false;
16✔
259
      }
16✔
260
      else {
6,117✔
261
        if (zone->hasQNamePolicies()) {
6,117✔
262
          allEmpty = false;
319✔
263
        }
319✔
264
        else {
5,798✔
265
          enabled = false;
5,798✔
266
        }
5,798✔
267
      }
6,117✔
268
    }
6,133✔
269

270
    zoneEnabled[count] = enabled;
6,147✔
271
    ++count;
6,147✔
272
  }
6,147✔
273

274
  if (allEmpty) {
22,929✔
275
    return false;
22,624✔
276
  }
22,624✔
277

278
  /* prepare the wildcard-based names */
279
  std::vector<DNSName> wcNames;
305✔
280
  wcNames.reserve(qname.countLabels());
305✔
281
  DNSName sub(qname);
305✔
282
  while (sub.chopOff()) {
1,005✔
283
    wcNames.emplace_back(g_wildcarddnsname + sub);
700✔
284
  }
700✔
285

286
  count = 0;
305✔
287
  for (const auto& zone : d_zones) {
314✔
288
    if (!zoneEnabled[count]) {
313✔
289
      ++count;
4✔
290
      continue;
4✔
291
    }
4✔
292

293
    if (zone->findExactQNamePolicy(qname, pol)) {
309✔
294
      // cerr<<"Had a hit on the name of the query"<<endl;
295
      return true;
118✔
296
    }
118✔
297

298
    for (const auto& wildcard : wcNames) {
403✔
299
      if (zone->findExactQNamePolicy(wildcard, pol)) {
403✔
300
        // cerr<<"Had a hit on the name of the query"<<endl;
301
        // Hit is not the wildcard passed to findExactQNamePolicy but the actual qname!
302
        pol.d_hitdata->d_hit = qname.toStringNoDot();
18✔
303
        return true;
18✔
304
      }
18✔
305
    }
403✔
306

307
    ++count;
173✔
308
  }
173✔
309

310
  return false;
169✔
311
}
305✔
312

313
bool DNSFilterEngine::getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
314
{
27,874✔
315
  for (const auto& record : records) {
72,469✔
316
    if (getPostPolicy(record, discardedPolicies, pol)) {
72,469✔
317
      return true;
13✔
318
    }
13✔
319
  }
72,469✔
320

321
  return false;
27,861✔
322
}
27,874✔
323

324
bool DNSFilterEngine::getPostPolicy(const DNSRecord& record, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
325
{
72,468✔
326
  ComboAddress address;
72,468✔
327
  if (record.d_place != DNSResourceRecord::ANSWER) {
72,468✔
328
    return false;
14,894✔
329
  }
14,894✔
330

331
  if (record.d_type == QType::A) {
57,574✔
332
    if (auto rec = getRR<ARecordContent>(record)) {
11,644!
333
      address = rec->getCA();
11,644✔
334
    }
11,644✔
335
  }
11,644✔
336
  else if (record.d_type == QType::AAAA) {
45,930✔
337
    if (auto rec = getRR<AAAARecordContent>(record)) {
47!
338
      address = rec->getCA();
47✔
339
    }
47✔
340
  }
47✔
341
  else {
45,883✔
342
    return false;
45,883✔
343
  }
45,883✔
344

345
  for (const auto& zone : d_zones) {
11,691✔
346
    if (zone->getPriority() >= pol.getPriority()) {
671✔
347
      break;
15✔
348
    }
15✔
349
    const auto& zoneName = zone->getName();
656✔
350
    if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
656✔
351
      return false;
6✔
352
    }
6✔
353

354
    if (zone->findResponsePolicy(address, pol)) {
650✔
355
      return true;
13✔
356
    }
13✔
357
  }
650✔
358

359
  return false;
11,672✔
360
}
11,691✔
361

362
void DNSFilterEngine::assureZones(size_t zone)
363
{
13✔
364
  if (d_zones.size() <= zone) {
13!
365
    d_zones.resize(zone + 1);
×
366
  }
×
367
}
13✔
368

369
static void addCustom(DNSFilterEngine::Policy& existingPol, const DNSFilterEngine::Policy& pol)
370
{
31✔
371
  if (!existingPol.d_custom) {
31!
372
    existingPol.d_custom = make_unique<DNSFilterEngine::Policy::CustomData>();
×
373
  }
×
374
  if (pol.d_custom) {
31!
375
    existingPol.d_custom->reserve(existingPol.d_custom->size() + pol.d_custom->size());
31✔
376
    std::move(pol.d_custom->begin(), pol.d_custom->end(), std::back_inserter(*existingPol.d_custom));
31✔
377
  }
31✔
378
}
31✔
379

380
void DNSFilterEngine::Zone::addNameTrigger(std::unordered_map<DNSName, Policy>& map, const DNSName& n, Policy&& pol, bool ignoreDuplicate, PolicyType ptype)
381
{
126✔
382
  auto iter = map.find(n);
126✔
383

384
  if (iter != map.end()) {
126✔
385
    auto& existingPol = iter->second;
21✔
386

387
    if (pol.d_kind != PolicyKind::Custom && !ignoreDuplicate) {
21!
388
      if (d_zoneData->d_ignoreDuplicates) {
4✔
389
        return;
2✔
390
      }
2✔
391
      throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for the following name: " + n.toLogString());
2✔
392
    }
4✔
393

394
    if (existingPol.d_kind != PolicyKind::Custom && !ignoreDuplicate) {
17!
395
      if (d_zoneData->d_ignoreDuplicates) {
2!
396
        return;
×
397
      }
×
398
      throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for for the following name: " + n.toLogString());
2✔
399
    }
2✔
400

401
    addCustom(existingPol, pol);
15✔
402
  }
15✔
403
  else {
105✔
404
    auto& qpol = map.insert({n, std::move(pol)}).first->second;
105✔
405
    qpol.d_zoneData = d_zoneData;
105✔
406
    qpol.d_type = ptype;
105✔
407
  }
105✔
408
}
126✔
409

410
void DNSFilterEngine::Zone::addNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& netmask, Policy&& pol, bool ignoreDuplicate, PolicyType ptype)
411
{
60✔
412
  bool exists = nmt.has_key(netmask);
60✔
413

414
  if (exists) {
60✔
415
    auto& existingPol = nmt.lookup(netmask)->second;
16✔
416

417
    if (pol.d_kind != PolicyKind::Custom && !ignoreDuplicate) {
16!
418
      if (d_zoneData->d_ignoreDuplicates) {
×
419
        return;
×
420
      }
×
421
      throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for the following netmask: " + netmask.toString());
×
422
    }
×
423

424
    if (existingPol.d_kind != PolicyKind::Custom && !ignoreDuplicate) {
16!
425
      if (d_zoneData->d_ignoreDuplicates) {
×
426
        return;
×
427
      }
×
428
      throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for the following netmask: " + netmask.toString());
×
429
    }
×
430

431
    addCustom(existingPol, pol);
16✔
432
  }
16✔
433
  else {
44✔
434
    pol.d_zoneData = d_zoneData;
44✔
435
    pol.d_type = ptype;
44✔
436
    nmt.insert(netmask).second = std::move(pol);
44✔
437
  }
44✔
438
}
60✔
439

440
bool DNSFilterEngine::Zone::rmNameTrigger(std::unordered_map<DNSName, Policy>& map, const DNSName& name, const Policy& pol)
441
{
18✔
442
  auto found = map.find(name);
18✔
443
  if (found == map.end()) {
18!
444
    return false;
×
445
  }
×
446

447
  auto& existing = found->second;
18✔
448
  if (existing.d_kind != DNSFilterEngine::PolicyKind::Custom) {
18✔
449
    map.erase(found);
10✔
450
    return true;
10✔
451
  }
10✔
452

453
  /* for custom types, we might have more than one type,
454
     and then we need to remove only the right ones. */
455
  bool result = false;
8✔
456
  if (pol.d_custom && existing.d_custom) {
8!
457
    for (const auto& toRemove : *pol.d_custom) {
8✔
458
      for (auto it = existing.d_custom->begin(); it != existing.d_custom->end(); ++it) {
9!
459
        if (**it == *toRemove) {
9✔
460
          existing.d_custom->erase(it);
8✔
461
          result = true;
8✔
462
          break;
8✔
463
        }
8✔
464
      }
9✔
465
    }
8✔
466
  }
8✔
467

468
  // No records left for this trigger?
469
  if (existing.customRecordsSize() == 0) {
8✔
470
    map.erase(found);
4✔
471
    return true;
4✔
472
  }
4✔
473

474
  return result;
4✔
475
}
8✔
476

477
bool DNSFilterEngine::Zone::rmNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& netmask, const Policy& pol)
478
{
20✔
479
  bool found = nmt.has_key(netmask);
20✔
480
  if (!found) {
20✔
481
    return false;
4✔
482
  }
4✔
483

484
  auto& existing = nmt.lookup(netmask)->second;
16✔
485
  if (existing.d_kind != DNSFilterEngine::PolicyKind::Custom) {
16✔
486
    nmt.erase(netmask);
6✔
487
    return true;
6✔
488
  }
6✔
489

490
  /* for custom types, we might have more than one type,
491
     and then we need to remove only the right ones. */
492

493
  bool result = false;
10✔
494
  if (pol.d_custom && existing.d_custom) {
10!
495
    for (const auto& toRemove : *pol.d_custom) {
10✔
496
      for (auto it = existing.d_custom->begin(); it != existing.d_custom->end(); ++it) {
20✔
497
        if (**it == *toRemove) {
16✔
498
          existing.d_custom->erase(it);
6✔
499
          result = true;
6✔
500
          break;
6✔
501
        }
6✔
502
      }
16✔
503
    }
10✔
504
  }
10✔
505

506
  // No records left for this trigger?
507
  if (existing.customRecordsSize() == 0) {
10✔
508
    nmt.erase(netmask);
2✔
509
    return true;
2✔
510
  }
2✔
511

512
  return result;
8✔
513
}
10✔
514

515
void DNSFilterEngine::Zone::addClientTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate)
516
{
12✔
517
  addNetmaskTrigger(d_qpolAddr, netmask, std::move(pol), ignoreDuplicate, PolicyType::ClientIP);
12✔
518
}
12✔
519

520
void DNSFilterEngine::Zone::addResponseTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate)
521
{
32✔
522
  addNetmaskTrigger(d_postpolAddr, netmask, std::move(pol), ignoreDuplicate, PolicyType::ResponseIP);
32✔
523
}
32✔
524

525
void DNSFilterEngine::Zone::addQNameTrigger(const DNSName& dnsname, Policy&& pol, bool ignoreDuplicate)
526
{
112✔
527
  addNameTrigger(d_qpolName, dnsname, std::move(pol), ignoreDuplicate, PolicyType::QName);
112✔
528
}
112✔
529

530
void DNSFilterEngine::Zone::addNSTrigger(const DNSName& dnsname, Policy&& pol, bool ignoreDuplicate)
531
{
14✔
532
  addNameTrigger(d_propolName, dnsname, std::move(pol), ignoreDuplicate, PolicyType::NSDName);
14✔
533
}
14✔
534

535
void DNSFilterEngine::Zone::addNSIPTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate)
536
{
16✔
537
  addNetmaskTrigger(d_propolNSAddr, netmask, std::move(pol), ignoreDuplicate, PolicyType::NSIP);
16✔
538
}
16✔
539

540
bool DNSFilterEngine::Zone::rmClientTrigger(const Netmask& netmask, const Policy& pol)
541
{
16✔
542
  return rmNetmaskTrigger(d_qpolAddr, netmask, pol);
16✔
543
}
16✔
544

545
bool DNSFilterEngine::Zone::rmResponseTrigger(const Netmask& netmask, const Policy& pol)
546
{
2✔
547
  return rmNetmaskTrigger(d_postpolAddr, netmask, pol);
2✔
548
}
2✔
549

550
bool DNSFilterEngine::Zone::rmQNameTrigger(const DNSName& dnsname, const Policy& pol)
551
{
14✔
552
  return rmNameTrigger(d_qpolName, dnsname, pol);
14✔
553
}
14✔
554

555
bool DNSFilterEngine::Zone::rmNSTrigger(const DNSName& dnsname, const Policy& pol)
556
{
4✔
557
  return rmNameTrigger(d_propolName, dnsname, pol);
4✔
558
}
4✔
559

560
bool DNSFilterEngine::Zone::rmNSIPTrigger(const Netmask& netmask, const Policy& pol)
561
{
2✔
562
  return rmNetmaskTrigger(d_propolNSAddr, netmask, pol);
2✔
563
}
2✔
564

565
std::string DNSFilterEngine::Policy::getLogString() const
566
{
101✔
567
  return ": RPZ Hit; PolicyName=" + getName() + "; Trigger=" + getTrigger().toLogString() + "; Hit=" + getHit() + "; Type=" + getTypeToString(d_type) + "; Kind=" + getKindToString(d_kind);
101✔
568
}
101✔
569

570
void DNSFilterEngine::Policy::info(Logr::Priority prio, const std::shared_ptr<Logr::Logger>& log) const
571
{
6✔
572
  log->info(prio, "RPZ Hit", "policyName", Logging::Loggable(getName()), "trigger", Logging::Loggable(getTrigger()),
6✔
573
            "hit", Logging::Loggable(getHit()), "type", Logging::Loggable(getTypeToString(d_type)),
6✔
574
            "kind", Logging::Loggable(getKindToString(d_kind)));
6✔
575
}
6✔
576

577
DNSRecord DNSFilterEngine::Policy::getRecordFromCustom(const DNSName& qname, const std::shared_ptr<const DNSRecordContent>& custom) const
578
{
130✔
579
  DNSRecord dnsrecord;
130✔
580
  dnsrecord.d_name = qname;
130✔
581
  dnsrecord.d_type = custom->getType();
130✔
582
  dnsrecord.d_ttl = d_ttl;
130✔
583
  dnsrecord.d_class = QClass::IN;
130✔
584
  dnsrecord.d_place = DNSResourceRecord::ANSWER;
130✔
585
  dnsrecord.setContent(custom);
130✔
586

587
  if (dnsrecord.d_type == QType::CNAME) {
130✔
588
    const auto content = std::dynamic_pointer_cast<const CNAMERecordContent>(custom);
36✔
589
    if (content) {
36!
590
      DNSName target = content->getTarget();
36✔
591
      if (target.isWildcard()) {
36!
592
        target.chopOff();
×
593
        dnsrecord.setContent(std::make_shared<CNAMERecordContent>(qname + target));
×
594
      }
×
595
    }
36✔
596
  }
36✔
597

598
  return dnsrecord;
130✔
599
}
130✔
600

601
std::vector<DNSRecord> DNSFilterEngine::Policy::getCustomRecords(const DNSName& qname, uint16_t qtype) const
602
{
125✔
603
  if (d_kind != PolicyKind::Custom) {
125!
604
    throw std::runtime_error("Asking for a custom record from a filtering policy of a non-custom type");
×
605
  }
×
606

607
  std::vector<DNSRecord> result;
125✔
608
  if (customRecordsSize() == 0) {
125!
609
    return result;
×
610
  }
×
611

612
  for (const auto& custom : *d_custom) {
200✔
613
    if (qtype != QType::ANY && qtype != custom->getType() && custom->getType() != QType::CNAME) {
200✔
614
      continue;
70✔
615
    }
70✔
616

617
    DNSRecord dnsrecord;
130✔
618
    dnsrecord.d_name = qname;
130✔
619
    dnsrecord.d_type = custom->getType();
130✔
620
    dnsrecord.d_ttl = d_ttl;
130✔
621
    dnsrecord.d_class = QClass::IN;
130✔
622
    dnsrecord.d_place = DNSResourceRecord::ANSWER;
130✔
623
    dnsrecord.setContent(custom);
130✔
624

625
    if (dnsrecord.d_type == QType::CNAME) {
130✔
626
      const auto content = std::dynamic_pointer_cast<const CNAMERecordContent>(custom);
36✔
627
      if (content) {
36!
628
        DNSName target = content->getTarget();
36✔
629
        if (target.isWildcard()) {
36!
630
          target.chopOff();
×
631
          dnsrecord.setContent(std::make_shared<CNAMERecordContent>(qname + target));
×
632
        }
×
633
      }
36✔
634
    }
36✔
635

636
    result.emplace_back(getRecordFromCustom(qname, custom));
130✔
637
  }
130✔
638

639
  return result;
125✔
640
}
125✔
641

642
std::string DNSFilterEngine::getKindToString(DNSFilterEngine::PolicyKind kind)
643
{
119✔
644
  // static const std::string rpzPrefix("rpz-");
645

646
  switch (kind) {
119✔
647
  case DNSFilterEngine::PolicyKind::NoAction:
26✔
648
    return rpzNoActionName;
26✔
649
  case DNSFilterEngine::PolicyKind::Drop:
10✔
650
    return rpzDropName;
10✔
651
  case DNSFilterEngine::PolicyKind::NXDOMAIN:
6✔
652
    return g_rootdnsname.toString();
6✔
653
  case PolicyKind::NODATA:
4✔
654
    return g_wildcarddnsname.toString();
4✔
655
  case DNSFilterEngine::PolicyKind::Truncate:
8✔
656
    return rpzTruncateName;
8✔
657
  case DNSFilterEngine::PolicyKind::Custom:
65✔
658
    return rpzCustomName;
65✔
659
  default:
×
660
    throw std::runtime_error("Unexpected DNSFilterEngine::Policy kind");
×
661
  }
119✔
662
}
119✔
663

664
std::string DNSFilterEngine::getTypeToString(DNSFilterEngine::PolicyType type)
665
{
111✔
666
  switch (type) {
111✔
667
  case DNSFilterEngine::PolicyType::None:
×
668
    return "none";
×
669
  case DNSFilterEngine::PolicyType::QName:
100✔
670
    return "QName";
100✔
671
  case DNSFilterEngine::PolicyType::ClientIP:
×
672
    return "Client IP";
×
673
  case DNSFilterEngine::PolicyType::ResponseIP:
9✔
674
    return "Response IP";
9✔
675
  case DNSFilterEngine::PolicyType::NSDName:
×
676
    return "Name Server Name";
×
677
  case DNSFilterEngine::PolicyType::NSIP:
2✔
678
    return "Name Server IP";
2✔
679
  default:
×
680
    throw std::runtime_error("Unexpected DNSFilterEngine::Policy type");
×
681
  }
111✔
682
}
111✔
683

684
std::vector<DNSRecord> DNSFilterEngine::Policy::getRecords(const DNSName& qname) const
685
{
16✔
686
  std::vector<DNSRecord> result;
16✔
687

688
  if (d_kind == PolicyKind::Custom) {
16✔
689
    result = getCustomRecords(qname, QType::ANY);
12✔
690
  }
12✔
691
  else {
4✔
692
    DNSRecord dnsrecord;
4✔
693
    dnsrecord.d_name = qname;
4✔
694
    dnsrecord.d_ttl = static_cast<uint32_t>(d_ttl);
4✔
695
    dnsrecord.d_type = QType::CNAME;
4✔
696
    dnsrecord.d_class = QClass::IN;
4✔
697
    dnsrecord.setContent(DNSRecordContent::make(QType::CNAME, QClass::IN, getKindToString(d_kind)));
4✔
698
    result.push_back(std::move(dnsrecord));
4✔
699
  }
4✔
700

701
  return result;
16✔
702
}
16✔
703

704
void DNSFilterEngine::Zone::dumpNamedPolicy(FILE* filePtr, const DNSName& name, const Policy& pol)
705
{
16✔
706
  auto records = pol.getRecords(name);
16✔
707
  for (const auto& record : records) {
19✔
708
    fprintf(filePtr, "%s %" PRIu32 " IN %s %s\n", record.d_name.toString().c_str(), record.d_ttl, QType(record.d_type).toString().c_str(), record.getContent()->getZoneRepresentation().c_str());
19✔
709
  }
19✔
710
}
16✔
711

712
DNSName DNSFilterEngine::Zone::maskToRPZ(const Netmask& netmask)
713
{
62✔
714
  int bits = netmask.getBits();
62✔
715
  DNSName res(std::to_string(bits));
62✔
716
  const auto& addr = netmask.getNetwork();
62✔
717

718
  if (addr.isIPv4()) {
62✔
719
    const auto* bytes = reinterpret_cast<const uint8_t*>(&addr.sin4.sin_addr.s_addr); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
42✔
720
    res += DNSName(std::to_string(bytes[3]) + "." + std::to_string(bytes[2]) + "." + std::to_string(bytes[1]) + "." + std::to_string(bytes[0])); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
42✔
721
  }
42✔
722
  else {
20✔
723
    DNSName temp;
20✔
724
    static_assert(sizeof(addr.sin6.sin6_addr.s6_addr) == sizeof(uint16_t) * 8);
20✔
725
    const auto* src = reinterpret_cast<const uint16_t*>(&addr.sin6.sin6_addr.s6_addr); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
20✔
726
    std::array<uint16_t, 8> elems{};
20✔
727

728
    // this routine was adopted from libc's inet_ntop6, written by Paul Vixie
729
    // because the RPZ spec (https://datatracker.ietf.org/doc/html/draft-vixie-dnsop-dns-rpz-00#section-4.1.1) says:
730
    //
731
    //    If there exists more than one sequence of zero-valued fields of
732
    //    identical length, then only the last such sequence is compressed.
733
    //    Note that [RFC5952] specifies compressing the first such sequence,
734
    //    but our notation here reverses the order of fields, and so must also
735
    //    reverse the selection of which zero sequence to compress.
736
    //
737
    // 'cur.len > best.len' from the original code is replaced by 'cur.len >= best.len', so the last-longest wins.
738

739
    struct
20✔
740
    {
20✔
741
      int base, len;
20✔
742
    } best = {-1, 0}, cur = {-1, 0};
20✔
743

744
    const int size = elems.size();
20✔
745
    for (int i = 0; i < size; i++) {
180✔
746
      elems.at(i) = ntohs(src[i]); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
160✔
747
      if (elems.at(i) == 0) {
160✔
748
        if (cur.base == -1) { // start of a run of zeroes
104✔
749
          cur = {i, 1};
26✔
750
        }
26✔
751
        else {
78✔
752
          cur.len++; // continuation of a run of zeroes
78✔
753
        }
78✔
754
      }
104✔
755
      else { // not a zero
56✔
756
        if (cur.base != -1) { // end of a run of zeroes
56✔
757
          if (best.base == -1 || cur.len >= best.len) { // first run of zeroes, or a better one than we found before
12!
758
            best = cur;
12✔
759
          }
12✔
760
          cur.base = -1;
12✔
761
        }
12✔
762
      }
56✔
763
    }
160✔
764

765
    if (cur.base != -1) { // address ended with a zero
20✔
766
      if (best.base == -1 || cur.len >= best.len) { // first run of zeroes, or a better one than we found before
14✔
767
        best = cur;
12✔
768
      }
12✔
769
    }
14✔
770

771
    if (best.base != -1 && best.len < 2) { // if our best run is only one zero long, we do not replace it
20!
772
      best.base = -1;
2✔
773
    }
2✔
774
    for (int i = 0; i < (int)elems.size(); i++) {
110✔
775
      if (i == best.base) {
90✔
776
        temp = DNSName("zz") + temp;
18✔
777
        i = i + best.len - 1;
18✔
778
      }
18✔
779
      else {
72✔
780
        temp = DNSName((boost::format("%x") % elems.at(i)).str()) + temp;
72✔
781
      }
72✔
782
    }
90✔
783
    res += temp;
20✔
784
  }
20✔
785

786
  return res;
62✔
787
}
62✔
788

789
void DNSFilterEngine::Zone::dumpAddrPolicy(FILE* filePtr, const Netmask& netmask, const DNSName& name, const Policy& pol)
790
{
×
791
  DNSName full = maskToRPZ(netmask);
×
792
  full += name;
×
793

794
  auto records = pol.getRecords(full);
×
795
  for (const auto& record : records) {
×
796
    fprintf(filePtr, "%s %" PRIu32 " IN %s %s\n", record.d_name.toString().c_str(), record.d_ttl, QType(record.d_type).toString().c_str(), record.getContent()->getZoneRepresentation().c_str());
×
797
  }
×
798
}
×
799

800
void DNSFilterEngine::Zone::dump(FILE* filePtr) const
801
{
10✔
802
  if (DNSRecord soa = d_zoneData->d_soa; !soa.d_name.empty()) {
10!
803
    fprintf(filePtr, "%s IN SOA %s\n", soa.d_name.toString().c_str(), soa.getContent()->getZoneRepresentation().c_str());
10✔
804
  }
10✔
805
  else {
×
806
    /* fake the SOA record */
807
    auto soarr = DNSRecordContent::make(QType::SOA, QClass::IN, "fake.RPZ. hostmaster.fake.RPZ. " + std::to_string(d_serial) + " " + std::to_string(d_refresh) + " 600 3600000 604800");
×
808
    fprintf(filePtr, "%s IN SOA %s\n", d_domain.toString().c_str(), soarr->getZoneRepresentation().c_str());
×
809
  }
×
810

811
  for (const auto& pair : d_qpolName) {
16✔
812
    dumpNamedPolicy(filePtr, pair.first + d_domain, pair.second);
16✔
813
  }
16✔
814

815
  for (const auto& pair : d_propolName) {
10!
816
    dumpNamedPolicy(filePtr, pair.first + DNSName(rpzNSDnameName) + d_domain, pair.second);
×
817
  }
×
818

819
  for (const auto& pair : d_qpolAddr) {
10!
820
    dumpAddrPolicy(filePtr, pair.first, DNSName(rpzClientIPName) + d_domain, pair.second);
×
821
  }
×
822

823
  for (const auto& pair : d_propolNSAddr) {
10!
824
    dumpAddrPolicy(filePtr, pair.first, DNSName(rpzNSIPName) + d_domain, pair.second);
×
825
  }
×
826

827
  for (const auto& pair : d_postpolAddr) {
10!
828
    dumpAddrPolicy(filePtr, pair.first, DNSName(rpzIPName) + d_domain, pair.second);
×
829
  }
×
830
}
10✔
831

832
void mergePolicyTags(std::unordered_set<std::string>& tags, const std::unordered_set<std::string>& newTags)
833
{
118✔
834
  for (const auto& tag : newTags) {
118✔
835
    tags.insert(tag);
2✔
836
  }
2✔
837
}
118✔
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