1 #ifndef BOOST_TEST_DYN_LINK
2 #define BOOST_TEST_DYN_LINK
5 #include <boost/test/unit_test.hpp>
7 #include "aggressive_nsec.hh"
8 #include "test-syncres_cc.hh"
10 BOOST_AUTO_TEST_SUITE(aggressive_nsec_cc
)
12 BOOST_AUTO_TEST_CASE(test_small_coverering_nsec3
)
14 AggressiveNSECCache::s_maxNSEC3CommonPrefix
= 1;
16 const std::vector
<std::tuple
<string
, string
, uint8_t, bool>> table
= {
17 {"gujhshp2lhmnpoo9qde4blg4gq3hgl99", "gujhshp2lhmnpoo9qde4blg4gq3hgl9a", 157, true},
18 {"gujhshp2lhmnpoo9qde4blg4gq3hgl99", "gujhshp2lhmnpoo9qde4blg4gq3hgl9a", 158, false},
19 {"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "vujhshp2lhmnpoo9qde4blg4gq3hgl9a", 0, false},
20 {"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "7ujhshp2lhmnpoo9qde4blg4gq3hgl9a", 1, true},
21 {"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "7ujhshp2lhmnpoo9qde4blg4gq3hgl9a", 2, false},
22 {"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "fujhshp2lhmnpoo9qde4blg4gq3hgl9a", 1, false},
23 {"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "8ujhshp2lhmnpoo9qde4blg4gq3hgl9a", 1, false},
24 {"8ujhshp2lhmnpoo9qde4blg4gq3hgl99", "8ujhshp2lhmnpoo9qde4blg4gq3hgl99", 0, false},
25 {"8ujhshp2lhmnpoo9qde4blg4gq3hgl99", "8ujhshp2lhmnpoo9qde4blg4gq3hgl99", 1, false},
26 {"8ujhshp2lhmnpoo9qde4blg4gq3hgl99", "8ujhshp2lhmnpoo9qde4blg4gq3hgl99", 157, false},
29 for (const auto& [owner
, next
, boundary
, result
] : table
) {
30 AggressiveNSECCache::s_maxNSEC3CommonPrefix
= boundary
;
31 BOOST_CHECK_EQUAL(AggressiveNSECCache::isSmallCoveringNSEC3(DNSName(owner
), fromBase32Hex(next
)), result
);
35 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nxdomain
)
37 std::unique_ptr
<SyncRes
> sr
;
39 g_aggressiveNSECCache
= make_unique
<AggressiveNSECCache
>(10000);
41 setDNSSECValidation(sr
, DNSSECMode::ValidateAll
);
44 /* we first ask b.powerdns.com., get a NXD, then check that the aggressive
45 NSEC cache will use the NSEC (a -> h) to prove that g.powerdns.com. does not exist
47 const DNSName
target1("b.powerdns.com.");
48 const DNSName
target2("g.powerdns.com.");
51 auto luaconfsCopy
= g_luaconfs
.getCopy();
52 luaconfsCopy
.dsAnchors
.clear();
53 generateKeyMaterial(g_rootdnsname
, DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
, luaconfsCopy
.dsAnchors
);
54 generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
);
56 g_luaconfs
.setState(luaconfsCopy
);
58 size_t queriesCount
= 0;
60 sr
->setAsyncCallback([target1
, &queriesCount
, keys
](const ComboAddress
& ip
, const DNSName
& domain
, int type
, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval
* /* now */, boost::optional
<Netmask
>& /* srcmask */, boost::optional
<const ResolveContext
&> /* context */, LWResult
* res
, bool* /* chained */) {
63 if (type
== QType::DS
|| type
== QType::DNSKEY
) {
64 if (domain
!= DNSName("powerdns.com.") && domain
.isPartOf(DNSName("powerdns.com."))) {
65 /* no cut, NSEC, name does not exist (the generic version will generate an exact NSEC for the target, which we don't want) */
66 setLWResult(res
, RCode::NoError
, true, false, true);
68 addRecordToLW(res
, DNSName("powerdns.com."), QType::SOA
, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY
, 3600);
69 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
70 /* no record for this name */
71 addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("h.powerdns.com."), {QType::A
, QType::TXT
, QType::RRSIG
}, 600, res
->d_records
);
72 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
73 /* no wildcard either */
74 addNSECRecordToLW(DNSName(").powerdns.com."), DNSName("a.powerdns.com."), {QType::AAAA
, QType::RRSIG
}, 600, res
->d_records
);
75 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com"), 300);
76 return LWResult::Result::Success
;
78 else if (domain
== DNSName("com.")) {
80 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
, false);
82 else if (domain
== DNSName("powerdns.com.")) {
83 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
);
87 return genericDSAndDNSKEYHandler(res
, domain
, domain
, type
, keys
);
91 if (isRootServer(ip
)) {
92 setLWResult(res
, 0, false, false, true);
93 addRecordToLW(res
, "powerdns.com.", QType::NS
, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY
, 3600);
94 addDS(DNSName("powerdns.com."), 300, res
->d_records
, keys
);
95 addRRSIG(keys
, res
->d_records
, DNSName("."), 300);
96 addRecordToLW(res
, "a.gtld-servers.com.", QType::A
, "192.0.2.1", DNSResourceRecord::ADDITIONAL
, 3600);
97 return LWResult::Result::Success
;
99 else if (ip
== ComboAddress("192.0.2.1:53")) {
100 if (domain
== target1
) {
101 setLWResult(res
, RCode::NXDomain
, true, false, true);
102 addRecordToLW(res
, DNSName("powerdns.com."), QType::SOA
, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY
, 3600);
103 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
104 /* no record for this name */
105 addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("h.powerdns.com."), {QType::A
, QType::TXT
, QType::RRSIG
}, 600, res
->d_records
);
106 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
107 /* no wildcard either */
108 addNSECRecordToLW(DNSName(").powerdns.com."), DNSName("a.powerdns.com."), {QType::AAAA
, QType::RRSIG
}, 600, res
->d_records
);
109 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com"), 300);
110 return LWResult::Result::Success
;
115 return LWResult::Result::Timeout
;
118 vector
<DNSRecord
> ret
;
119 int res
= sr
->beginResolve(target1
, QType(QType::A
), QClass::IN
, ret
);
120 BOOST_CHECK_EQUAL(res
, RCode::NXDomain
);
121 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
122 BOOST_REQUIRE_EQUAL(ret
.size(), 6U);
123 BOOST_CHECK_EQUAL(queriesCount
, 4U);
126 res
= sr
->beginResolve(target2
, QType(QType::A
), QClass::IN
, ret
);
127 BOOST_CHECK_EQUAL(res
, RCode::NXDomain
);
128 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
129 BOOST_REQUIRE_EQUAL(ret
.size(), 6U);
130 BOOST_CHECK_EQUAL(queriesCount
, 4U);
133 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nodata
)
135 std::unique_ptr
<SyncRes
> sr
;
137 g_aggressiveNSECCache
= make_unique
<AggressiveNSECCache
>(10000);
139 setDNSSECValidation(sr
, DNSSECMode::ValidateAll
);
142 /* we first ask a.powerdns.com. | A, get a NODATA, then check that the aggressive
143 NSEC cache will use the NSEC to prove that the AAAA does not exist either */
144 const DNSName
target("a.powerdns.com.");
147 auto luaconfsCopy
= g_luaconfs
.getCopy();
148 luaconfsCopy
.dsAnchors
.clear();
149 generateKeyMaterial(g_rootdnsname
, DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
, luaconfsCopy
.dsAnchors
);
150 generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
);
152 g_luaconfs
.setState(luaconfsCopy
);
154 size_t queriesCount
= 0;
156 sr
->setAsyncCallback([target
, &queriesCount
, keys
](const ComboAddress
& ip
, const DNSName
& domain
, int type
, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval
* /* now */, boost::optional
<Netmask
>& /* srcmask */, boost::optional
<const ResolveContext
&> /* context */, LWResult
* res
, bool* /* chained */) {
159 if (type
== QType::DS
|| type
== QType::DNSKEY
) {
160 if (domain
!= DNSName("powerdns.com.") && domain
.isPartOf(DNSName("powerdns.com."))) {
162 return genericDSAndDNSKEYHandler(res
, domain
, domain
, type
, keys
, false);
164 else if (domain
== DNSName("com.")) {
166 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
, false);
168 else if (domain
== DNSName("powerdns.com.")) {
169 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
);
173 return genericDSAndDNSKEYHandler(res
, domain
, domain
, type
, keys
);
177 if (isRootServer(ip
)) {
178 setLWResult(res
, 0, false, false, true);
179 addRecordToLW(res
, "powerdns.com.", QType::NS
, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY
, 3600);
180 addDS(DNSName("powerdns.com."), 300, res
->d_records
, keys
);
181 addRRSIG(keys
, res
->d_records
, DNSName("."), 300);
182 addRecordToLW(res
, "a.gtld-servers.com.", QType::A
, "192.0.2.1", DNSResourceRecord::ADDITIONAL
, 3600);
183 return LWResult::Result::Success
;
185 else if (ip
== ComboAddress("192.0.2.1:53")) {
186 if (domain
== target
&& type
== QType::A
) {
187 setLWResult(res
, RCode::NoError
, true, false, true);
189 addRecordToLW(res
, DNSName("powerdns.com."), QType::SOA
, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY
, 3600);
190 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
191 /* no record for this name */
193 addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), {QType::TXT
, QType::RRSIG
}, 600, res
->d_records
);
194 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
195 /* no need for wildcard in that case */
196 return LWResult::Result::Success
;
201 return LWResult::Result::Timeout
;
204 vector
<DNSRecord
> ret
;
205 int res
= sr
->beginResolve(target
, QType(QType::A
), QClass::IN
, ret
);
206 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
207 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
208 BOOST_REQUIRE_EQUAL(ret
.size(), 4U);
209 BOOST_CHECK_EQUAL(queriesCount
, 4U);
212 res
= sr
->beginResolve(target
, QType(QType::AAAA
), QClass::IN
, ret
);
213 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
214 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
215 BOOST_REQUIRE_EQUAL(ret
.size(), 4U);
216 BOOST_CHECK_EQUAL(queriesCount
, 4U);
219 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nodata_wildcard
)
221 std::unique_ptr
<SyncRes
> sr
;
223 g_aggressiveNSECCache
= make_unique
<AggressiveNSECCache
>(10000);
225 setDNSSECValidation(sr
, DNSSECMode::ValidateAll
);
228 /* we first ask a.powerdns.com. | A, get a NODATA (no exact match but there is a wildcard match),
229 then check that the aggressive NSEC cache will use the NSEC to prove that the AAAA does not exist either */
230 const DNSName
target("a.powerdns.com.");
233 auto luaconfsCopy
= g_luaconfs
.getCopy();
234 luaconfsCopy
.dsAnchors
.clear();
235 generateKeyMaterial(g_rootdnsname
, DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
, luaconfsCopy
.dsAnchors
);
236 generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
);
238 g_luaconfs
.setState(luaconfsCopy
);
240 size_t queriesCount
= 0;
242 sr
->setAsyncCallback([target
, &queriesCount
, keys
](const ComboAddress
& ip
, const DNSName
& domain
, int type
, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval
* /* now */, boost::optional
<Netmask
>& /* srcmask */, boost::optional
<const ResolveContext
&> /* context */, LWResult
* res
, bool* /* chained */) {
245 if (type
== QType::DS
|| type
== QType::DNSKEY
) {
246 if (domain
!= DNSName("powerdns.com.") && domain
.isPartOf(DNSName("powerdns.com."))) {
247 /* no cut, NSEC, name does not exist but there is a wildcard (the generic version will generate an exact NSEC for the target, which we don't want) */
248 setLWResult(res
, RCode::NoError
, true, false, true);
250 addRecordToLW(res
, DNSName("powerdns.com."), QType::SOA
, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY
, 3600);
251 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
252 /* the name does not exist, a wildcard applies but does not have this type */
253 addNSECRecordToLW(DNSName("*.powerdns.com."), DNSName("z.powerdns.com."), {QType::TXT
, QType::RRSIG
}, 600, res
->d_records
);
254 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300, false, boost::none
, DNSName("*.powerdns.com"));
255 return LWResult::Result::Success
;
257 else if (domain
== DNSName("com.")) {
259 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
, false);
261 else if (domain
== DNSName("powerdns.com.")) {
262 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
);
266 return genericDSAndDNSKEYHandler(res
, domain
, domain
, type
, keys
);
270 if (isRootServer(ip
)) {
271 setLWResult(res
, 0, false, false, true);
272 addRecordToLW(res
, "powerdns.com.", QType::NS
, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY
, 3600);
273 addDS(DNSName("powerdns.com."), 300, res
->d_records
, keys
);
274 addRRSIG(keys
, res
->d_records
, DNSName("."), 300);
275 addRecordToLW(res
, "a.gtld-servers.com.", QType::A
, "192.0.2.1", DNSResourceRecord::ADDITIONAL
, 3600);
276 return LWResult::Result::Success
;
278 else if (ip
== ComboAddress("192.0.2.1:53")) {
279 if (domain
== target
&& type
== QType::A
) {
280 setLWResult(res
, RCode::NoError
, true, false, true);
282 addRecordToLW(res
, DNSName("powerdns.com."), QType::SOA
, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY
, 3600);
283 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
284 /* the name does not exist, a wildcard applies but does not have this type */
285 addNSECRecordToLW(DNSName("*.powerdns.com."), DNSName("z.powerdns.com."), {QType::TXT
, QType::RRSIG
}, 600, res
->d_records
);
286 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300, false, boost::none
, DNSName("*.powerdns.com"));
287 return LWResult::Result::Success
;
292 return LWResult::Result::Timeout
;
295 vector
<DNSRecord
> ret
;
296 int res
= sr
->beginResolve(target
, QType(QType::A
), QClass::IN
, ret
);
297 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
298 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
299 BOOST_REQUIRE_EQUAL(ret
.size(), 4U);
300 BOOST_CHECK_EQUAL(queriesCount
, 4U);
303 res
= sr
->beginResolve(target
, QType(QType::AAAA
), QClass::IN
, ret
);
304 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
305 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
306 BOOST_REQUIRE_EQUAL(ret
.size(), 4U);
307 BOOST_CHECK_EQUAL(queriesCount
, 4U);
310 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor
)
312 std::unique_ptr
<SyncRes
> sr
;
314 g_aggressiveNSECCache
= make_unique
<AggressiveNSECCache
>(10000);
316 setDNSSECValidation(sr
, DNSSECMode::ValidateAll
);
319 /* powerdns.com is signed, sub.powerdns.com. is not.
320 We first get a query for sub.powerdns.com. which leads to an ancestor NSEC covering sub.powerdns.com.|DS to be inserted
321 into the aggressive cache, check that we don't mistakenly use that later to prove that something else below that name
322 doesn't exist either. */
323 const DNSName
target("sub.powerdns.com.");
326 auto luaconfsCopy
= g_luaconfs
.getCopy();
327 luaconfsCopy
.dsAnchors
.clear();
328 generateKeyMaterial(g_rootdnsname
, DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
, luaconfsCopy
.dsAnchors
);
329 generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
);
331 g_luaconfs
.setState(luaconfsCopy
);
333 size_t queriesCount
= 0;
335 sr
->setAsyncCallback([target
, &queriesCount
, keys
](const ComboAddress
& ip
, const DNSName
& domain
, int type
, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval
* /* now */, boost::optional
<Netmask
>& /* srcmask */, boost::optional
<const ResolveContext
&> /* context */, LWResult
* res
, bool* /* chained */) {
338 if (type
== QType::DS
|| type
== QType::DNSKEY
) {
339 if (domain
== DNSName("com.")) {
341 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
, false);
345 return genericDSAndDNSKEYHandler(res
, domain
, domain
, type
, keys
);
349 if (isRootServer(ip
)) {
350 setLWResult(res
, 0, false, false, true);
351 addRecordToLW(res
, "powerdns.com.", QType::NS
, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY
, 3600);
352 addDS(DNSName("powerdns.com."), 300, res
->d_records
, keys
);
353 addRRSIG(keys
, res
->d_records
, DNSName("."), 300);
354 addRecordToLW(res
, "a.gtld-servers.com.", QType::A
, "192.0.2.1", DNSResourceRecord::ADDITIONAL
, 3600);
355 return LWResult::Result::Success
;
357 else if (ip
== ComboAddress("192.0.2.1:53")) {
358 if (domain
.isPartOf(DNSName("sub.powerdns.com."))) {
359 setLWResult(res
, 0, false, false, true);
360 addRecordToLW(res
, "sub.powerdns.com.", QType::NS
, "ns.sub.powerdns.com.", DNSResourceRecord::AUTHORITY
, 3600);
361 /* proof that the DS doesn't exist follows */
362 /* NSEC ancestor for sub.powerdns.com */
363 addNSECRecordToLW(DNSName("sub.powerdns.com."), DNSName("sub1.powerdns.com"), {QType::NS
}, 600, res
->d_records
);
364 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
365 addRecordToLW(res
, "ns.sub.powerdns.com.", QType::A
, "192.0.2.2", DNSResourceRecord::ADDITIONAL
, 3600);
366 return LWResult::Result::Success
;
368 else if (domain
== DNSName("sub16.powerdns.com.")) {
369 setLWResult(res
, RCode::NXDomain
, true, false, true);
370 addRecordToLW(res
, DNSName("powerdns.com."), QType::SOA
, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY
, 3600);
371 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
372 addNSECRecordToLW(DNSName("sub15.powerdns.com."), DNSName("sub17.powerdns.com."), {QType::A
}, 600, res
->d_records
);
373 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
374 /* then the wildcard *.powerdns.com
375 next covers the wildcard *.sub.powerdns.com
377 addNSECRecordToLW(DNSName(").powerdns.com"), DNSName("+.sub.powerdns.com"), {QType::TXT
, QType::RRSIG
}, 600, res
->d_records
);
378 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
379 return LWResult::Result::Success
;
382 else if (ip
== ComboAddress("192.0.2.2:53")) {
383 if (domain
== target
&& type
== QType::A
) {
384 setLWResult(res
, RCode::NoError
, true, false, true);
385 addRecordToLW(res
, DNSName("sub.powerdns.com."), QType::A
, "192.0.2.42");
386 return LWResult::Result::Success
;
388 else if (domain
== DNSName("4.sub.powerdns.com.") && type
== QType::A
) {
389 setLWResult(res
, RCode::NoError
, true, false, true);
390 addRecordToLW(res
, DNSName("4.sub.powerdns.com."), QType::A
, "192.0.2.84");
391 return LWResult::Result::Success
;
396 return LWResult::Result::Timeout
;
399 vector
<DNSRecord
> ret
;
400 int res
= sr
->beginResolve(target
, QType(QType::A
), QClass::IN
, ret
);
401 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
402 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Insecure
);
403 BOOST_REQUIRE_EQUAL(ret
.size(), 1U);
404 BOOST_CHECK_EQUAL(queriesCount
, 5U);
406 /* now we query sub16.powerdns.com, to get a NSEC covering the wildcard for *.sub.powerdns.com */
408 res
= sr
->beginResolve(DNSName("sub16.powerdns.com."), QType(QType::A
), QClass::IN
, ret
);
409 BOOST_CHECK_EQUAL(res
, RCode::NXDomain
);
410 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
411 BOOST_REQUIRE_EQUAL(ret
.size(), 6U);
412 BOOST_CHECK_EQUAL(queriesCount
, 6U);
414 /* now we query other2.sub.powerdns.com, we should NOT be able to use the NSECs we have
415 to prove that the name does not exist */
417 res
= sr
->beginResolve(DNSName("4.sub.powerdns.com"), QType(QType::DS
), QClass::IN
, ret
);
418 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
419 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Insecure
);
420 BOOST_REQUIRE_EQUAL(ret
.size(), 1U);
421 BOOST_CHECK_EQUAL(queriesCount
, 8U);
424 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_wildcard_synthesis
)
426 std::unique_ptr
<SyncRes
> sr
;
428 g_aggressiveNSECCache
= make_unique
<AggressiveNSECCache
>(10000);
430 setDNSSECValidation(sr
, DNSSECMode::ValidateAll
);
433 /* we first ask a.powerdns.com. | A, get an answer synthesized from the wildcard.
434 We can use it yet because we need the SOA, so let's request a non-existing type
435 then check that the aggressive NSEC cache will use the wildcard to synthesize an answer
436 for b.powerdns.com */
437 const DNSName
target("a.powerdns.com.");
440 auto luaconfsCopy
= g_luaconfs
.getCopy();
441 luaconfsCopy
.dsAnchors
.clear();
442 generateKeyMaterial(g_rootdnsname
, DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
, luaconfsCopy
.dsAnchors
);
443 generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
);
445 g_luaconfs
.setState(luaconfsCopy
);
447 size_t queriesCount
= 0;
449 sr
->setAsyncCallback([target
, &queriesCount
, keys
](const ComboAddress
& ip
, const DNSName
& domain
, int type
, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval
* /* now */, boost::optional
<Netmask
>& /* srcmask */, boost::optional
<const ResolveContext
&> /* context */, LWResult
* res
, bool* /* chained */) {
452 if (type
== QType::DS
|| type
== QType::DNSKEY
) {
453 if (domain
!= DNSName("powerdns.com.") && domain
.isPartOf(DNSName("powerdns.com."))) {
454 /* no cut, NSEC, name does not exist but there is a wildcard (the generic version will generate an exact NSEC for the target, which we don't want) */
455 setLWResult(res
, RCode::NoError
, true, false, true);
457 addRecordToLW(res
, DNSName("powerdns.com."), QType::SOA
, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY
, 3600);
458 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
459 /* the name does not exist, a wildcard applies and have the requested type but no DS */
460 addNSECRecordToLW(DNSName("*.powerdns.com."), DNSName("z.powerdns.com."), {QType::A
, QType::RRSIG
}, 600, res
->d_records
);
461 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300, false, boost::none
, DNSName("*.powerdns.com"));
462 return LWResult::Result::Success
;
464 else if (domain
== DNSName("com.")) {
466 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
, false);
468 else if (domain
== DNSName("powerdns.com.")) {
469 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
);
473 return genericDSAndDNSKEYHandler(res
, domain
, domain
, type
, keys
);
477 if (isRootServer(ip
)) {
478 setLWResult(res
, 0, false, false, true);
479 addRecordToLW(res
, "powerdns.com.", QType::NS
, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY
, 3600);
480 addDS(DNSName("powerdns.com."), 300, res
->d_records
, keys
);
481 addRRSIG(keys
, res
->d_records
, DNSName("."), 300);
482 addRecordToLW(res
, "a.gtld-servers.com.", QType::A
, "192.0.2.1", DNSResourceRecord::ADDITIONAL
, 3600);
483 return LWResult::Result::Success
;
485 else if (ip
== ComboAddress("192.0.2.1:53")) {
486 if (type
== QType::A
) {
487 setLWResult(res
, RCode::NoError
, true, false, true);
488 addRecordToLW(res
, domain
, QType::A
, "192.0.2.1");
489 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300, false, boost::none
, DNSName("*.powerdns.com"));
490 /* the name does not exist, a wildcard applies and has the requested type */
491 addNSECRecordToLW(DNSName("*.powerdns.com."), DNSName("z.powerdns.com."), {QType::A
, QType::RRSIG
}, 600, res
->d_records
);
492 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300, false, boost::none
, DNSName("*.powerdns.com"));
493 return LWResult::Result::Success
;
495 else if (type
== QType::TXT
) {
496 setLWResult(res
, RCode::NoError
, true, false, true);
497 /* the name does not exist, a wildcard applies but does not have the requested type */
498 addRecordToLW(res
, DNSName("powerdns.com."), QType::SOA
, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY
);
499 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
500 addNSECRecordToLW(DNSName("*.powerdns.com."), DNSName("z.powerdns.com."), {QType::A
, QType::RRSIG
}, 600, res
->d_records
);
501 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300, false, boost::none
, DNSName("*.powerdns.com"));
502 return LWResult::Result::Success
;
507 return LWResult::Result::Timeout
;
510 vector
<DNSRecord
> ret
;
511 int res
= sr
->beginResolve(target
, QType(QType::A
), QClass::IN
, ret
);
512 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
513 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
514 BOOST_REQUIRE_EQUAL(ret
.size(), 4U);
515 BOOST_CHECK_EQUAL(ret
.at(0).d_name
, target
);
516 BOOST_CHECK_EQUAL(ret
.at(0).d_type
, QType(QType::A
).getCode());
517 BOOST_CHECK_EQUAL(queriesCount
, 4U);
519 /* request the TXT to get the SOA */
521 res
= sr
->beginResolve(target
, QType(QType::TXT
), QClass::IN
, ret
);
522 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
523 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
524 BOOST_REQUIRE_EQUAL(ret
.size(), 4U);
525 BOOST_CHECK_EQUAL(ret
.at(0).d_name
, DNSName("powerdns.com."));
526 BOOST_CHECK_EQUAL(ret
.at(0).d_type
, QType(QType::SOA
).getCode());
527 BOOST_CHECK_EQUAL(queriesCount
, 5U);
530 res
= sr
->beginResolve(DNSName("b.powerdns.com."), QType(QType::A
), QClass::IN
, ret
);
531 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
532 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
533 BOOST_REQUIRE_EQUAL(ret
.size(), 4U);
534 BOOST_CHECK_EQUAL(ret
.at(0).d_name
, DNSName("b.powerdns.com."));
535 BOOST_CHECK_EQUAL(ret
.at(0).d_type
, QType(QType::A
).getCode());
536 BOOST_CHECK_EQUAL(queriesCount
, 5U);
539 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nxdomain
)
541 std::unique_ptr
<SyncRes
> sr
;
543 AggressiveNSECCache::s_maxNSEC3CommonPrefix
= 159;
544 g_aggressiveNSECCache
= make_unique
<AggressiveNSECCache
>(10000);
546 setDNSSECValidation(sr
, DNSSECMode::ValidateAll
);
549 /* we are lucky enough that our hashes will cover g.powerdns.com. as well,
550 so we first ask b.powerdns.com., get a NXD, then check that the aggressive
551 NSEC cache will use the NSEC3 to prove that g.powerdns.com. does not exist
553 const DNSName
target1("b.powerdns.com.");
554 const DNSName
target2("g.powerdns.com.");
557 auto luaconfsCopy
= g_luaconfs
.getCopy();
558 luaconfsCopy
.dsAnchors
.clear();
559 generateKeyMaterial(g_rootdnsname
, DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
, luaconfsCopy
.dsAnchors
);
560 generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
);
562 g_luaconfs
.setState(luaconfsCopy
);
564 size_t queriesCount
= 0;
566 sr
->setAsyncCallback([target1
, &queriesCount
, keys
](const ComboAddress
& ip
, const DNSName
& domain
, int type
, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval
* /* now */, boost::optional
<Netmask
>& /* srcmask */, boost::optional
<const ResolveContext
&> /* context */, LWResult
* res
, bool* /* chained */) {
569 if (type
== QType::DS
|| type
== QType::DNSKEY
) {
570 if (domain
!= DNSName("powerdns.com.") && domain
.isPartOf(DNSName("powerdns.com."))) {
572 setLWResult(res
, RCode::NXDomain
, true, false, true);
573 addRecordToLW(res
, DNSName("powerdns.com."), QType::SOA
, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY
, 3600);
574 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
575 /* no record for this name */
576 /* first the closest encloser */
577 addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A
, QType::TXT
, QType::RRSIG
}, 600, res
->d_records
);
578 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
579 /* then the next closer */
580 addNSEC3UnhashedRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG
}, 600, res
->d_records
);
581 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
583 addNSEC3NarrowRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), {QType::AAAA
, QType::RRSIG
}, 600, res
->d_records
);
584 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com"), 300);
585 return LWResult::Result::Success
;
587 else if (domain
== DNSName("com.")) {
589 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
, false);
591 else if (domain
== DNSName("powerdns.com.")) {
592 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
);
596 return genericDSAndDNSKEYHandler(res
, domain
, domain
, type
, keys
);
600 if (isRootServer(ip
)) {
601 setLWResult(res
, 0, false, false, true);
602 addRecordToLW(res
, "powerdns.com.", QType::NS
, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY
, 3600);
603 addDS(DNSName("powerdns.com."), 300, res
->d_records
, keys
);
604 addRRSIG(keys
, res
->d_records
, DNSName("."), 300);
605 addRecordToLW(res
, "a.gtld-servers.com.", QType::A
, "192.0.2.1", DNSResourceRecord::ADDITIONAL
, 3600);
606 return LWResult::Result::Success
;
608 else if (ip
== ComboAddress("192.0.2.1:53")) {
609 if (domain
== target1
) {
610 setLWResult(res
, RCode::NXDomain
, true, false, true);
611 addRecordToLW(res
, DNSName("powerdns.com."), QType::SOA
, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY
, 3600);
612 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
613 /* no record for this name */
614 /* first the closest encloser */
615 addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A
, QType::TXT
, QType::RRSIG
}, 600, res
->d_records
);
616 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
617 /* then the next closer */
618 addNSEC3UnhashedRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG
}, 600, res
->d_records
);
619 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
621 addNSEC3NarrowRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), {QType::AAAA
, QType::RRSIG
}, 600, res
->d_records
);
622 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com"), 300);
623 return LWResult::Result::Success
;
628 return LWResult::Result::Timeout
;
631 vector
<DNSRecord
> ret
;
632 int res
= sr
->beginResolve(target1
, QType(QType::A
), QClass::IN
, ret
);
633 BOOST_CHECK_EQUAL(res
, RCode::NXDomain
);
634 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
635 BOOST_REQUIRE_EQUAL(ret
.size(), 8U);
636 BOOST_CHECK_EQUAL(queriesCount
, 4U);
639 res
= sr
->beginResolve(target2
, QType(QType::A
), QClass::IN
, ret
);
640 BOOST_CHECK_EQUAL(res
, RCode::NXDomain
);
641 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
642 BOOST_REQUIRE_EQUAL(ret
.size(), 8U);
643 BOOST_CHECK_EQUAL(queriesCount
, 4U);
646 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata
)
648 std::unique_ptr
<SyncRes
> sr
;
650 g_aggressiveNSECCache
= make_unique
<AggressiveNSECCache
>(10000);
652 setDNSSECValidation(sr
, DNSSECMode::ValidateAll
);
655 /* we first ask a.powerdns.com. | A, get a NODATA, then check that the aggressive
656 NSEC cache will use the NSEC3 to prove that the AAAA does not exist either */
657 const DNSName
target("a.powerdns.com.");
660 auto luaconfsCopy
= g_luaconfs
.getCopy();
661 luaconfsCopy
.dsAnchors
.clear();
662 generateKeyMaterial(g_rootdnsname
, DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
, luaconfsCopy
.dsAnchors
);
663 generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
);
665 g_luaconfs
.setState(luaconfsCopy
);
667 size_t queriesCount
= 0;
669 sr
->setAsyncCallback([target
, &queriesCount
, keys
](const ComboAddress
& ip
, const DNSName
& domain
, int type
, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval
* /* now */, boost::optional
<Netmask
>& /* srcmask */, boost::optional
<const ResolveContext
&> /* context */, LWResult
* res
, bool* /* chained */) {
672 if (type
== QType::DS
|| type
== QType::DNSKEY
) {
673 if (domain
!= DNSName("powerdns.com.") && domain
.isPartOf(DNSName("powerdns.com."))) {
675 return genericDSAndDNSKEYHandler(res
, domain
, domain
, type
, keys
, false, boost::none
, true);
677 else if (domain
== DNSName("com.")) {
679 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
, false);
681 else if (domain
== DNSName("powerdns.com.")) {
682 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
);
686 return genericDSAndDNSKEYHandler(res
, domain
, domain
, type
, keys
);
690 if (isRootServer(ip
)) {
691 setLWResult(res
, 0, false, false, true);
692 addRecordToLW(res
, "powerdns.com.", QType::NS
, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY
, 3600);
693 addDS(DNSName("powerdns.com."), 300, res
->d_records
, keys
);
694 addRRSIG(keys
, res
->d_records
, DNSName("."), 300);
695 addRecordToLW(res
, "a.gtld-servers.com.", QType::A
, "192.0.2.1", DNSResourceRecord::ADDITIONAL
, 3600);
696 return LWResult::Result::Success
;
698 else if (ip
== ComboAddress("192.0.2.1:53")) {
699 if (domain
== target
&& type
== QType::A
) {
700 setLWResult(res
, RCode::NoError
, true, false, true);
702 addRecordToLW(res
, DNSName("powerdns.com."), QType::SOA
, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY
, 3600);
703 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
704 /* no record for this name */
706 addNSEC3UnhashedRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::TXT
, QType::RRSIG
}, 600, res
->d_records
);
707 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
708 /* no need for next closer or wildcard in that case */
709 return LWResult::Result::Success
;
714 return LWResult::Result::Timeout
;
717 vector
<DNSRecord
> ret
;
718 int res
= sr
->beginResolve(target
, QType(QType::A
), QClass::IN
, ret
);
719 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
720 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
721 BOOST_REQUIRE_EQUAL(ret
.size(), 4U);
722 BOOST_CHECK_EQUAL(queriesCount
, 4U);
725 res
= sr
->beginResolve(target
, QType(QType::AAAA
), QClass::IN
, ret
);
726 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
727 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
728 BOOST_REQUIRE_EQUAL(ret
.size(), 4U);
729 BOOST_CHECK_EQUAL(queriesCount
, 4U);
732 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata_wildcard
)
734 std::unique_ptr
<SyncRes
> sr
;
736 AggressiveNSECCache::s_maxNSEC3CommonPrefix
= 159;
737 g_aggressiveNSECCache
= make_unique
<AggressiveNSECCache
>(10000);
739 setDNSSECValidation(sr
, DNSSECMode::ValidateAll
);
742 /* we first ask a.powerdns.com. | A, get a NODATA (no exact match but there is a wildcard match),
743 then check that the aggressive NSEC cache will use the NSEC3 to prove that the AAAA does not exist either */
744 const DNSName
target("a.powerdns.com.");
747 auto luaconfsCopy
= g_luaconfs
.getCopy();
748 luaconfsCopy
.dsAnchors
.clear();
749 generateKeyMaterial(g_rootdnsname
, DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
, luaconfsCopy
.dsAnchors
);
750 generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
);
752 g_luaconfs
.setState(luaconfsCopy
);
754 size_t queriesCount
= 0;
756 sr
->setAsyncCallback([target
, &queriesCount
, keys
](const ComboAddress
& ip
, const DNSName
& domain
, int type
, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval
* /* now */, boost::optional
<Netmask
>& /* srcmask */, boost::optional
<const ResolveContext
&> /* context */, LWResult
* res
, bool* /* chained */) {
759 if (type
== QType::DS
|| type
== QType::DNSKEY
) {
760 if (domain
!= DNSName("powerdns.com.") && domain
.isPartOf(DNSName("powerdns.com."))) {
761 /* no cut, NSEC3, name does not exist but there is a wildcard (the generic version will generate an exact NSEC3 for the target, which we don't want) */
762 setLWResult(res
, RCode::NoError
, true, false, true);
764 addRecordToLW(res
, DNSName("powerdns.com."), QType::SOA
, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY
, 3600);
765 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
766 /* first the closest encloser */
767 addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A
, QType::TXT
, QType::RRSIG
}, 600, res
->d_records
, 10);
768 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
769 /* then the next closer */
770 addNSEC3UnhashedRecordToLW(DNSName("+.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG
}, 600, res
->d_records
, 10);
771 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
772 /* a wildcard applies but does not have this type */
773 addNSEC3UnhashedRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::TXT
, QType::RRSIG
}, 600, res
->d_records
, 10);
774 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com"), 300, false, boost::none
, DNSName("*.powerdns.com"));
775 return LWResult::Result::Success
;
777 else if (domain
== DNSName("com.")) {
779 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
, false);
781 else if (domain
== DNSName("powerdns.com.")) {
782 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
);
786 return genericDSAndDNSKEYHandler(res
, domain
, domain
, type
, keys
);
790 if (isRootServer(ip
)) {
791 setLWResult(res
, 0, false, false, true);
792 addRecordToLW(res
, "powerdns.com.", QType::NS
, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY
, 3600);
793 addDS(DNSName("powerdns.com."), 300, res
->d_records
, keys
);
794 addRRSIG(keys
, res
->d_records
, DNSName("."), 300);
795 addRecordToLW(res
, "a.gtld-servers.com.", QType::A
, "192.0.2.1", DNSResourceRecord::ADDITIONAL
, 3600);
796 return LWResult::Result::Success
;
798 else if (ip
== ComboAddress("192.0.2.1:53")) {
799 if (domain
== target
&& type
== QType::A
) {
800 setLWResult(res
, RCode::NoError
, true, false, true);
802 addRecordToLW(res
, DNSName("powerdns.com."), QType::SOA
, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY
, 3600);
803 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
804 /* first the closest encloser */
805 addNSEC3NoDataNarrowRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), {QType::A
, QType::TXT
, QType::RRSIG
}, 600, res
->d_records
, 10);
806 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
807 /* then the next closer */
808 addNSEC3NarrowRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), {QType::RRSIG
}, 600, res
->d_records
, 10);
809 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
810 /* a wildcard applies but does not have this type */
811 addNSEC3NoDataNarrowRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), {QType::TXT
, QType::RRSIG
}, 600, res
->d_records
, 10);
812 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com"), 300, false, boost::none
, DNSName("*.powerdns.com"));
813 return LWResult::Result::Success
;
818 return LWResult::Result::Timeout
;
821 vector
<DNSRecord
> ret
;
822 int res
= sr
->beginResolve(target
, QType(QType::A
), QClass::IN
, ret
);
823 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
824 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
825 BOOST_REQUIRE_EQUAL(ret
.size(), 8U);
826 BOOST_CHECK_EQUAL(queriesCount
, 4U);
829 res
= sr
->beginResolve(target
, QType(QType::AAAA
), QClass::IN
, ret
);
830 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
831 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
832 BOOST_REQUIRE_EQUAL(ret
.size(), 8U);
833 BOOST_CHECK_EQUAL(queriesCount
, 4U);
836 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor
)
838 std::unique_ptr
<SyncRes
> sr
;
840 g_aggressiveNSECCache
= make_unique
<AggressiveNSECCache
>(10000);
842 setDNSSECValidation(sr
, DNSSECMode::ValidateAll
);
845 /* powerdns.com is signed, sub.powerdns.com. is not.
846 We first get a query for sub.powerdns.com. which leads to an ancestor NSEC3 covering sub.powerdns.com.|DS to be inserted
847 into the aggressive cache, check that we don't mistakenly use that later to prove that something else below that name
848 doesn't exist either. */
849 const DNSName
target("sub.powerdns.com.");
852 auto luaconfsCopy
= g_luaconfs
.getCopy();
853 luaconfsCopy
.dsAnchors
.clear();
854 generateKeyMaterial(g_rootdnsname
, DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
, luaconfsCopy
.dsAnchors
);
855 generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
);
857 g_luaconfs
.setState(luaconfsCopy
);
859 size_t queriesCount
= 0;
861 sr
->setAsyncCallback([target
, &queriesCount
, keys
](const ComboAddress
& ip
, const DNSName
& domain
, int type
, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval
* /* now */, boost::optional
<Netmask
>& /* srcmask */, boost::optional
<const ResolveContext
&> /* context */, LWResult
* res
, bool* /* chained */) {
864 if (type
== QType::DS
|| type
== QType::DNSKEY
) {
865 if (domain
== DNSName("com.")) {
867 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
, false);
871 return genericDSAndDNSKEYHandler(res
, domain
, domain
, type
, keys
);
875 if (isRootServer(ip
)) {
876 setLWResult(res
, 0, false, false, true);
877 addRecordToLW(res
, "powerdns.com.", QType::NS
, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY
, 3600);
878 addDS(DNSName("powerdns.com."), 300, res
->d_records
, keys
);
879 addRRSIG(keys
, res
->d_records
, DNSName("."), 300);
880 addRecordToLW(res
, "a.gtld-servers.com.", QType::A
, "192.0.2.1", DNSResourceRecord::ADDITIONAL
, 3600);
881 return LWResult::Result::Success
;
883 else if (ip
== ComboAddress("192.0.2.1:53")) {
884 if (domain
.isPartOf(DNSName("sub.powerdns.com."))) {
885 setLWResult(res
, 0, false, false, true);
886 addRecordToLW(res
, "sub.powerdns.com.", QType::NS
, "ns.sub.powerdns.com.", DNSResourceRecord::AUTHORITY
, 3600);
887 /* proof that the DS doesn't exist follows */
888 /* NSEC3 ancestor for sub.powerdns.com (1 additional iteration, deadbeef as salt), : 7v5rgf7okrmumvb8rscop0t3j1m5o4mb
889 next is crafted to cover 4.sub.powerdns.com => 930v7tmju1s48fopjh5ktsp1jmagi20p */
890 addNSEC3RecordToLW(DNSName("7v5rgf7okrmumvb8rscop0t3j1m5o4mb.powerdns.com."), fromBase32Hex("930v7tmju1s48fopjh5ktsp1jmagi20q"), "deadbeef", 1, {QType::NS
}, 600, res
->d_records
);
891 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
892 addRecordToLW(res
, "ns.sub.powerdns.com.", QType::A
, "192.0.2.2", DNSResourceRecord::ADDITIONAL
, 3600);
893 return LWResult::Result::Success
;
895 else if (domain
== DNSName("sub16.powerdns.com.")) {
896 setLWResult(res
, RCode::NXDomain
, true, false, true);
897 addRecordToLW(res
, DNSName("powerdns.com."), QType::SOA
, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY
, 3600);
898 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
899 /* first the closest encloser */
900 addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::SOA
, QType::NS
}, 600, res
->d_records
, 1);
901 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
902 /* then the next closer sub16.powerdns.com. */
903 addNSEC3NarrowRecordToLW(DNSName("sub16.powerdns.com."), DNSName("powerdns.com."), {QType::A
}, 600, res
->d_records
, 1);
904 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
905 /* then the wildcard *.powerdns.com: mtrrinpd8l9e7fmn8lp74o8dffnivs8i (minus one because NXD)
906 next is crafted to cover the wildcard *.sub.powerdns.com (ocgb0ilk3g1m3olpms0q1quhn18nncc0)
908 addNSEC3RecordToLW(DNSName("mtrrinpd8l9e7fmn8lp74o8dffnivs8h.powerdns.com."), fromBase32Hex("ocgb0ilk3g1m3olpms0q1quhn18nncc1"), "deadbeef", 1, {QType::TXT
, QType::RRSIG
}, 600, res
->d_records
);
909 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
910 return LWResult::Result::Success
;
913 else if (ip
== ComboAddress("192.0.2.2:53")) {
914 if (domain
== target
&& type
== QType::A
) {
915 setLWResult(res
, RCode::NoError
, true, false, true);
916 addRecordToLW(res
, DNSName("sub.powerdns.com."), QType::A
, "192.0.2.42");
917 return LWResult::Result::Success
;
919 else if (domain
== DNSName("4.sub.powerdns.com.") && type
== QType::A
) {
920 setLWResult(res
, RCode::NoError
, true, false, true);
921 addRecordToLW(res
, DNSName("4.sub.powerdns.com."), QType::A
, "192.0.2.84");
922 return LWResult::Result::Success
;
927 return LWResult::Result::Timeout
;
930 vector
<DNSRecord
> ret
;
931 int res
= sr
->beginResolve(target
, QType(QType::A
), QClass::IN
, ret
);
932 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
933 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Insecure
);
934 BOOST_REQUIRE_EQUAL(ret
.size(), 1U);
935 BOOST_CHECK_EQUAL(queriesCount
, 5U);
937 /* now we query sub16.powerdns.com, to get a hash covering the wildcard for
938 *.sub.powerdns.com */
940 res
= sr
->beginResolve(DNSName("sub16.powerdns.com."), QType(QType::A
), QClass::IN
, ret
);
941 BOOST_CHECK_EQUAL(res
, RCode::NXDomain
);
942 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
943 BOOST_REQUIRE_EQUAL(ret
.size(), 8U);
944 BOOST_CHECK_EQUAL(queriesCount
, 6U);
946 /* now we query other2.sub.powerdns.com, we should NOT be able to use the NSEC3s we have
947 to prove that the name does not exist */
949 res
= sr
->beginResolve(DNSName("4.sub.powerdns.com"), QType(QType::DS
), QClass::IN
, ret
);
950 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
951 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Insecure
);
952 BOOST_REQUIRE_EQUAL(ret
.size(), 1U);
953 BOOST_CHECK_EQUAL(queriesCount
, 8U);
956 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_wildcard_synthesis
)
958 std::unique_ptr
<SyncRes
> sr
;
960 g_aggressiveNSECCache
= make_unique
<AggressiveNSECCache
>(10000);
962 setDNSSECValidation(sr
, DNSSECMode::ValidateAll
);
965 /* we first ask a.powerdns.com. | A, get an answer synthesized from the wildcard.
966 We can't use it right away because we don't have the SOA, so let's do a TXT query to get it,
967 then check that the aggressive NSEC cache will use the wildcard to synthesize an answer
968 for b.powerdns.com */
969 const DNSName
target("a.powerdns.com.");
972 auto luaconfsCopy
= g_luaconfs
.getCopy();
973 luaconfsCopy
.dsAnchors
.clear();
974 generateKeyMaterial(g_rootdnsname
, DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
, luaconfsCopy
.dsAnchors
);
975 generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256
, DNSSECKeeper::DIGEST_SHA256
, keys
);
977 g_luaconfs
.setState(luaconfsCopy
);
979 size_t queriesCount
= 0;
981 sr
->setAsyncCallback([target
, &queriesCount
, keys
](const ComboAddress
& ip
, const DNSName
& domain
, int type
, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval
* /* now */, boost::optional
<Netmask
>& /* srcmask */, boost::optional
<const ResolveContext
&> /* context */, LWResult
* res
, bool* /* chained */) {
984 if (type
== QType::DS
|| type
== QType::DNSKEY
) {
985 if (domain
!= DNSName("powerdns.com.") && domain
.isPartOf(DNSName("powerdns.com."))) {
986 /* no cut, NSEC3, name does not exist but there is a wildcard (the generic version will generate an exact NSEC3 for the target, which we don't want) */
987 setLWResult(res
, RCode::NoError
, true, false, true);
989 addRecordToLW(res
, DNSName("powerdns.com."), QType::SOA
, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY
, 3600);
990 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
991 /* first the closest encloser */
992 addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A
, QType::TXT
, QType::RRSIG
}, 600, res
->d_records
, 10);
993 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
994 /* then the next closer */
995 addNSEC3UnhashedRecordToLW(DNSName("+.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG
}, 600, res
->d_records
, 10);
996 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
997 /* a wildcard applies but does not have this type */
998 addNSEC3UnhashedRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A
, QType::RRSIG
}, 600, res
->d_records
, 10);
999 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com"), 300, false, boost::none
, DNSName("*.powerdns.com"));
1000 return LWResult::Result::Success
;
1002 else if (domain
== DNSName("com.")) {
1004 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
, false);
1006 else if (domain
== DNSName("powerdns.com.")) {
1007 return genericDSAndDNSKEYHandler(res
, domain
, DNSName("."), type
, keys
);
1011 return genericDSAndDNSKEYHandler(res
, domain
, domain
, type
, keys
);
1015 if (isRootServer(ip
)) {
1016 setLWResult(res
, 0, false, false, true);
1017 addRecordToLW(res
, "powerdns.com.", QType::NS
, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY
, 3600);
1018 addDS(DNSName("powerdns.com."), 300, res
->d_records
, keys
);
1019 addRRSIG(keys
, res
->d_records
, DNSName("."), 300);
1020 addRecordToLW(res
, "a.gtld-servers.com.", QType::A
, "192.0.2.1", DNSResourceRecord::ADDITIONAL
, 3600);
1021 return LWResult::Result::Success
;
1023 else if (ip
== ComboAddress("192.0.2.1:53")) {
1024 if (type
== QType::A
) {
1025 setLWResult(res
, RCode::NoError
, true, false, true);
1026 addRecordToLW(res
, domain
, QType::A
, "192.0.2.1");
1027 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300, false, boost::none
, DNSName("*.powerdns.com"));
1028 /* no need for the closest encloser since we have a positive answer expanded from a wildcard */
1029 /* the next closer */
1030 addNSEC3UnhashedRecordToLW(DNSName("+.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG
}, 600, res
->d_records
, 10);
1031 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
1032 /* and of course we don't deny the wildcard itself */
1033 return LWResult::Result::Success
;
1035 else if (type
== QType::TXT
) {
1036 setLWResult(res
, RCode::NoError
, true, false, true);
1037 /* the name does not exist, a wildcard applies but does not have the requested type */
1038 addRecordToLW(res
, DNSName("powerdns.com."), QType::SOA
, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY
);
1039 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
1040 /* the closest encloser */
1041 addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "v", {QType::SOA
, QType::NS
, QType::NSEC3
, QType::DNSKEY
, QType::RRSIG
}, 600, res
->d_records
, 10);
1042 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
1043 /* the next closer */
1044 addNSEC3UnhashedRecordToLW(DNSName("+.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG
}, 600, res
->d_records
, 10);
1045 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
1046 /* and the wildcard expanded unto itself */
1047 addNSEC3UnhashedRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), "v", {QType::A
}, 600, res
->d_records
, 10);
1048 addRRSIG(keys
, res
->d_records
, DNSName("powerdns.com."), 300);
1049 return LWResult::Result::Success
;
1054 return LWResult::Result::Timeout
;
1057 vector
<DNSRecord
> ret
;
1058 int res
= sr
->beginResolve(target
, QType(QType::A
), QClass::IN
, ret
);
1059 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
1060 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
1061 BOOST_REQUIRE_EQUAL(ret
.size(), 4U);
1062 BOOST_CHECK_EQUAL(ret
.at(0).d_name
, target
);
1063 BOOST_CHECK_EQUAL(ret
.at(0).d_type
, QType(QType::A
).getCode());
1064 BOOST_CHECK_EQUAL(queriesCount
, 4U);
1067 res
= sr
->beginResolve(target
, QType(QType::TXT
), QClass::IN
, ret
);
1068 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
1069 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
1070 BOOST_REQUIRE_EQUAL(ret
.size(), 8U);
1071 BOOST_CHECK_EQUAL(ret
.at(0).d_name
, DNSName("powerdns.com."));
1072 BOOST_CHECK_EQUAL(ret
.at(0).d_type
, QType(QType::SOA
).getCode());
1073 BOOST_CHECK_EQUAL(queriesCount
, 5U);
1076 res
= sr
->beginResolve(DNSName("b.powerdns.com."), QType(QType::A
), QClass::IN
, ret
);
1077 BOOST_CHECK_EQUAL(res
, RCode::NoError
);
1078 BOOST_CHECK_EQUAL(sr
->getValidationState(), vState::Secure
);
1079 BOOST_REQUIRE_EQUAL(ret
.size(), 4U);
1080 BOOST_CHECK_EQUAL(ret
.at(0).d_name
, DNSName("b.powerdns.com."));
1081 BOOST_CHECK_EQUAL(ret
.at(0).d_type
, QType(QType::A
).getCode());
1082 BOOST_CHECK_EQUAL(queriesCount
, 5U);
1085 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_replace
)
1087 const size_t testSize
= 10000;
1088 auto cache
= make_unique
<AggressiveNSECCache
>(testSize
);
1093 Utility::gettimeofday(&now
, nullptr);
1095 vector
<DNSName
> names
;
1096 names
.reserve(testSize
);
1097 for (size_t i
= 0; i
< testSize
; i
++) {
1098 names
.emplace_back(std::to_string(i
) + "powerdns.com");
1104 for (const auto& name
: names
) {
1107 rec
.d_type
= QType::NSEC3
;
1108 rec
.d_ttl
= now
.tv_sec
+ 10;
1109 rec
.setContent(getRecordContent(QType::NSEC3
, "1 0 500 ab HASG==== A RRSIG NSEC3"));
1110 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
1111 cache
->insertNSEC(DNSName("powerdns.com"), rec
.d_name
, rec
, {rrsig
}, true);
1113 auto diff1
= time
.udiff(true);
1115 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), testSize
);
1116 for (const auto& name
: names
) {
1119 rec
.d_type
= QType::NSEC3
;
1120 rec
.d_ttl
= now
.tv_sec
+ 10;
1121 rec
.setContent(getRecordContent(QType::NSEC3
, "1 0 500 ab HASG==== A RRSIG NSEC3"));
1122 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data");
1123 cache
->insertNSEC(DNSName("powerdns.com"), rec
.d_name
, rec
, {rrsig
}, true);
1126 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), testSize
);
1128 auto diff2
= time
.udiff(true);
1129 // Check that replace is about equally fast as insert
1130 BOOST_CHECK(diff1
< diff2
* 2 && diff2
< diff1
* 2);
1133 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_wiping
)
1135 auto cache
= make_unique
<AggressiveNSECCache
>(10000);
1138 Utility::gettimeofday(&now
, 0);
1141 rec
.d_name
= DNSName("www.powerdns.com");
1142 rec
.d_type
= QType::NSEC
;
1143 rec
.d_ttl
= now
.tv_sec
+ 10;
1144 rec
.setContent(getRecordContent(QType::NSEC
, "z.powerdns.com. A RRSIG NSEC"));
1145 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data");
1146 cache
->insertNSEC(DNSName("powerdns.com"), rec
.d_name
, rec
, {rrsig
}, false);
1148 rec
.d_name
= DNSName("z.powerdns.com");
1149 rec
.setContent(getRecordContent(QType::NSEC
, "zz.powerdns.com. AAAA RRSIG NSEC"));
1150 cache
->insertNSEC(DNSName("powerdns.com"), rec
.d_name
, rec
, {rrsig
}, false);
1152 rec
.d_name
= DNSName("www.powerdns.org");
1153 rec
.d_type
= QType::NSEC3
;
1154 rec
.d_ttl
= now
.tv_sec
+ 10;
1155 rec
.setContent(getRecordContent(QType::NSEC3
, "1 0 500 ab HASG==== A RRSIG NSEC3"));
1156 rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
1157 cache
->insertNSEC(DNSName("powerdns.org"), rec
.d_name
, rec
, {rrsig
}, true);
1159 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 3U);
1161 /* remove just that zone */
1162 cache
->removeZoneInfo(DNSName("powerdns.org"), false);
1163 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 2U);
1166 cache
->insertNSEC(DNSName("powerdns.org"), rec
.d_name
, rec
, {rrsig
}, true);
1167 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 3U);
1169 /* remove everything under .org (which should end up in the same way) */
1170 cache
->removeZoneInfo(DNSName("org."), true);
1171 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 2U);
1174 cache
->insertNSEC(DNSName("powerdns.org"), rec
.d_name
, rec
, {rrsig
}, true);
1175 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 3U);
1177 /* remove everything */
1178 cache
->removeZoneInfo(DNSName("."), true);
1179 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 0U);
1182 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_pruning
)
1184 auto cache
= make_unique
<AggressiveNSECCache
>(2);
1187 Utility::gettimeofday(&now
, 0);
1190 rec
.d_name
= DNSName("www.powerdns.com");
1191 rec
.d_type
= QType::NSEC
;
1192 rec
.d_ttl
= now
.tv_sec
+ 10;
1193 rec
.setContent(getRecordContent(QType::NSEC
, "z.powerdns.com. A RRSIG NSEC"));
1194 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data");
1195 cache
->insertNSEC(DNSName("powerdns.com"), rec
.d_name
, rec
, {rrsig
}, false);
1197 rec
.d_name
= DNSName("z.powerdns.com");
1198 rec
.setContent(getRecordContent(QType::NSEC
, "zz.powerdns.com. AAAA RRSIG NSEC"));
1199 cache
->insertNSEC(DNSName("powerdns.com"), rec
.d_name
, rec
, {rrsig
}, false);
1201 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 2U);
1202 /* we are at the limit of the number of entries, so we will scan 1/5th of the entries,
1203 and prune the expired ones, which mean we should not remove anything */
1204 cache
->prune(now
.tv_sec
);
1205 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 2U);
1207 rec
.d_name
= DNSName("www.powerdns.org");
1208 rec
.d_type
= QType::NSEC3
;
1209 rec
.d_ttl
= now
.tv_sec
+ 20;
1210 rec
.setContent(getRecordContent(QType::NSEC3
, "1 0 500 ab HASG==== A RRSIG NSEC3"));
1211 rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
1212 cache
->insertNSEC(DNSName("powerdns.org"), rec
.d_name
, rec
, {rrsig
}, true);
1214 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 3U);
1216 /* we have set a upper bound to 2 entries, so we are above,
1217 and one entry is actually expired, so we will prune one entry
1218 to get below the limit */
1219 cache
->prune(now
.tv_sec
+ 15);
1220 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 2U);
1222 /* now we are at the limit, so we will scan 1/10th of all zones entries, rounded up,
1223 and prune the expired ones, which mean we will also be removing the remaining two */
1224 cache
->prune(now
.tv_sec
+ 600);
1225 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 0U);
1228 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_dump
)
1230 auto cache
= make_unique
<AggressiveNSECCache
>(10000);
1232 std::vector
<std::string
> expected
;
1233 expected
.emplace_back("; Zone powerdns.com.\n");
1234 expected
.emplace_back("www.powerdns.com. 10 IN NSEC z.powerdns.com. A RRSIG NSEC\n");
1235 expected
.emplace_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
1236 expected
.emplace_back("z.powerdns.com. 10 IN NSEC zz.powerdns.com. AAAA RRSIG NSEC\n");
1237 expected
.emplace_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
1238 expected
.emplace_back("; Zone powerdns.org.\n");
1239 expected
.emplace_back("www.powerdns.org. 10 IN NSEC3 1 0 50 ab HASG==== A RRSIG NSEC3\n");
1240 expected
.emplace_back("- RRSIG NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
1245 Utility::gettimeofday(&now
, nullptr);
1248 rec
.d_name
= DNSName("www.powerdns.com");
1249 rec
.d_type
= QType::NSEC
;
1250 rec
.d_ttl
= now
.tv_sec
+ 10;
1251 rec
.setContent(getRecordContent(QType::NSEC
, "z.powerdns.com. A RRSIG NSEC"));
1252 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data");
1253 cache
->insertNSEC(DNSName("powerdns.com"), rec
.d_name
, rec
, {rrsig
}, false);
1255 rec
.d_name
= DNSName("z.powerdns.com");
1256 rec
.setContent(getRecordContent(QType::NSEC
, "zz.powerdns.com. AAAA RRSIG NSEC"));
1257 cache
->insertNSEC(DNSName("powerdns.com"), rec
.d_name
, rec
, {rrsig
}, false);
1259 rec
.d_name
= DNSName("www.powerdns.org");
1260 rec
.d_type
= QType::NSEC3
;
1261 rec
.d_ttl
= now
.tv_sec
+ 10;
1262 rec
.setContent(getRecordContent(QType::NSEC3
, "1 0 50 ab HASG==== A RRSIG NSEC3"));
1263 rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
1264 cache
->insertNSEC(DNSName("powerdns.org"), rec
.d_name
, rec
, {rrsig
}, true);
1266 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 3U);
1268 auto filePtr
= std::unique_ptr
<FILE, int (*)(FILE*)>(tmpfile(), fclose
);
1270 BOOST_FAIL("Temporary file could not be opened");
1273 BOOST_CHECK_EQUAL(cache
->dumpToFile(filePtr
, now
), 3U);
1275 rewind(filePtr
.get());
1276 char* line
= nullptr;
1279 for (const auto& str
: expected
) {
1280 auto read
= getline(&line
, &len
, filePtr
.get());
1282 BOOST_FAIL("Unable to read a line from the temp file");
1284 BOOST_CHECK_EQUAL(line
, str
);
1288 expected
.emplace_back("; Zone powerdns.com.\n");
1289 expected
.emplace_back("www.powerdns.com. 10 IN NSEC z.powerdns.com. A RRSIG NSEC\n");
1290 expected
.emplace_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
1291 expected
.emplace_back("z.powerdns.com. 30 IN NSEC zz.powerdns.com. AAAA RRSIG NSEC\n");
1292 expected
.emplace_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
1293 expected
.emplace_back("; Zone powerdns.org.\n");
1294 expected
.emplace_back("www.powerdns.org. 10 IN NSEC3 1 0 50 ab HASG==== A RRSIG NSEC3\n");
1295 expected
.emplace_back("- RRSIG NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
1297 rec
.d_name
= DNSName("z.powerdns.com");
1298 rec
.d_type
= QType::NSEC
;
1299 rec
.d_ttl
= now
.tv_sec
+ 30;
1300 rec
.setContent(getRecordContent(QType::NSEC
, "zz.powerdns.com. AAAA RRSIG NSEC"));
1301 rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data");
1302 cache
->insertNSEC(DNSName("powerdns.com"), rec
.d_name
, rec
, {rrsig
}, false);
1304 rewind(filePtr
.get());
1305 BOOST_CHECK_EQUAL(cache
->dumpToFile(filePtr
, now
), 3U);
1307 rewind(filePtr
.get());
1309 for (const auto& str
: expected
) {
1310 auto read
= getline(&line
, &len
, filePtr
.get());
1312 BOOST_FAIL("Unable to read a line from the temp file");
1314 BOOST_CHECK_EQUAL(line
, str
);
1317 /* getline() allocates a buffer when called with a nullptr,
1318 then reallocates it when needed, but we need to free the
1319 last allocation if any. */
1320 free(line
); // NOLINT: it's the API.
1323 static bool getDenialWrapper(std::unique_ptr
<AggressiveNSECCache
>& cache
, time_t now
, const DNSName
& name
, const QType
& qtype
, const std::optional
<int> expectedResult
= std::nullopt
, const std::optional
<size_t> expectedRecordsCount
= std::nullopt
)
1326 std::vector
<DNSRecord
> results
;
1327 pdns::validation::ValidationContext validationContext
;
1328 validationContext
.d_nsec3IterationsRemainingQuota
= std::numeric_limits
<decltype(validationContext
.d_nsec3IterationsRemainingQuota
)>::max();
1329 bool found
= cache
->getDenial(now
, name
, qtype
, results
, res
, ComboAddress("192.0.2.1"), boost::none
, true, validationContext
);
1330 if (expectedResult
) {
1331 BOOST_CHECK_EQUAL(res
, *expectedResult
);
1333 if (expectedRecordsCount
) {
1334 BOOST_CHECK_EQUAL(results
.size(), *expectedRecordsCount
);
1339 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_rollover
)
1341 /* test that we don't compare a hash using the wrong (former) salt or iterations count in case of a rollover,
1342 or when different servers use different parameters */
1343 AggressiveNSECCache::s_maxNSEC3CommonPrefix
= 159;
1344 auto cache
= make_unique
<AggressiveNSECCache
>(10000);
1345 g_recCache
= std::make_unique
<MemRecursorCache
>();
1347 const DNSName
zone("powerdns.com");
1348 time_t now
= time(nullptr);
1350 /* first we need a SOA */
1351 std::vector
<DNSRecord
> records
;
1352 time_t ttd
= now
+ 30;
1354 drSOA
.d_name
= zone
;
1355 drSOA
.d_type
= QType::SOA
;
1356 drSOA
.d_class
= QClass::IN
;
1357 drSOA
.setContent(std::make_shared
<SOARecordContent
>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
1358 drSOA
.d_ttl
= static_cast<uint32_t>(ttd
); // XXX truncation
1359 drSOA
.d_place
= DNSResourceRecord::ANSWER
;
1360 records
.push_back(drSOA
);
1362 g_recCache
->replace(now
, zone
, QType(QType::SOA
), records
, {}, {}, true, zone
, boost::none
, boost::none
, vState::Secure
);
1363 BOOST_CHECK_EQUAL(g_recCache
->size(), 1U);
1365 std::string oldSalt
= "ab";
1366 std::string newSalt
= "cd";
1367 unsigned int oldIterationsCount
= 2;
1368 unsigned int newIterationsCount
= 1;
1369 DNSName
name("www.powerdns.com");
1370 std::string hashed
= hashQNameWithSalt(oldSalt
, oldIterationsCount
, name
);
1373 rec
.d_name
= DNSName(toBase32Hex(hashed
)) + zone
;
1374 rec
.d_type
= QType::NSEC3
;
1375 rec
.d_ttl
= now
+ 10;
1377 NSEC3RecordContent nrc
;
1378 nrc
.d_algorithm
= 1;
1380 nrc
.d_iterations
= oldIterationsCount
;
1381 nrc
.d_salt
= oldSalt
;
1382 nrc
.d_nexthash
= hashed
;
1383 incrementHash(nrc
.d_nexthash
);
1384 for (const auto& type
: {QType::A
}) {
1388 rec
.setContent(std::make_shared
<NSEC3RecordContent
>(nrc
));
1389 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
1390 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, true);
1392 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 1U);
1394 /* we can use the NSEC3s we have */
1396 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::AAAA
), true);
1398 DNSName
other("other.powerdns.com");
1399 /* now we insert a new NSEC3, with a different salt, changing that value for the zone */
1400 hashed
= hashQNameWithSalt(newSalt
, oldIterationsCount
, other
);
1401 rec
.d_name
= DNSName(toBase32Hex(hashed
)) + zone
;
1402 rec
.d_type
= QType::NSEC3
;
1403 rec
.d_ttl
= now
+ 10;
1404 nrc
.d_algorithm
= 1;
1406 nrc
.d_iterations
= oldIterationsCount
;
1407 nrc
.d_salt
= newSalt
;
1408 nrc
.d_nexthash
= hashed
;
1409 incrementHash(nrc
.d_nexthash
);
1410 for (const auto& type
: {QType::A
}) {
1414 rec
.setContent(std::make_shared
<NSEC3RecordContent
>(nrc
));
1415 rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
1416 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, true);
1418 /* the existing entries should have been cleared */
1419 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 1U);
1421 /* we should be able to find a direct match for that name */
1423 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, other
, QType::AAAA
), true);
1425 /* but we should not be able to use the other NSEC3s */
1426 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::AAAA
), false);
1428 /* and the same thing but this time updating the iterations count instead of the salt */
1429 DNSName
other2("other2.powerdns.com");
1430 hashed
= hashQNameWithSalt(newSalt
, newIterationsCount
, other2
);
1431 rec
.d_name
= DNSName(toBase32Hex(hashed
)) + zone
;
1432 rec
.d_type
= QType::NSEC3
;
1433 rec
.d_ttl
= now
+ 10;
1434 nrc
.d_algorithm
= 1;
1436 nrc
.d_iterations
= newIterationsCount
;
1437 nrc
.d_salt
= newSalt
;
1438 nrc
.d_nexthash
= hashed
;
1439 incrementHash(nrc
.d_nexthash
);
1440 for (const auto& type
: {QType::A
}) {
1444 rec
.setContent(std::make_shared
<NSEC3RecordContent
>(nrc
));
1445 rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
1446 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, true);
1448 /* the existing entries should have been cleared */
1449 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 1U);
1451 /* we should be able to find a direct match for that name */
1453 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, other2
, QType::AAAA
), true);
1455 /* but we should not be able to use the other NSEC3s */
1456 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, other
, QType::AAAA
), false);
1459 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor_cases
)
1461 auto cache
= make_unique
<AggressiveNSECCache
>(10000);
1462 g_recCache
= std::make_unique
<MemRecursorCache
>();
1464 const DNSName
zone("powerdns.com");
1465 time_t now
= time(nullptr);
1467 /* first we need a SOA */
1468 std::vector
<DNSRecord
> records
;
1469 time_t ttd
= now
+ 30;
1471 drSOA
.d_name
= zone
;
1472 drSOA
.d_type
= QType::SOA
;
1473 drSOA
.d_class
= QClass::IN
;
1474 drSOA
.setContent(std::make_shared
<SOARecordContent
>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
1475 drSOA
.d_ttl
= static_cast<uint32_t>(ttd
); // XXX truncation
1476 drSOA
.d_place
= DNSResourceRecord::ANSWER
;
1477 records
.push_back(drSOA
);
1479 g_recCache
->replace(now
, zone
, QType(QType::SOA
), records
, {}, {}, true, zone
, boost::none
, boost::none
, vState::Secure
);
1480 BOOST_CHECK_EQUAL(g_recCache
->size(), 1U);
1483 cache
= make_unique
<AggressiveNSECCache
>(10000);
1484 /* insert a NSEC matching the exact name (apex) */
1485 DNSName
name("sub.powerdns.com");
1488 rec
.d_type
= QType::NSEC
;
1489 rec
.d_ttl
= now
+ 10;
1491 NSECRecordContent nrc
;
1492 nrc
.d_next
= DNSName("sub1.powerdns.com");
1493 for (const auto& type
: {QType::A
}) {
1497 rec
.setContent(std::make_shared
<NSECRecordContent
>(nrc
));
1498 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC 5 3 10 20370101000000 20370101000000 24567 sub.powerdns.com. data");
1499 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, false);
1501 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 1U);
1503 /* the cache should now be able to deny other types (except the DS) */
1504 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::AAAA
, RCode::NoError
, 3U), true);
1505 /* but not the DS that lives in the parent zone */
1506 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::DS
, std::nullopt
, 0U), false);
1510 cache
= make_unique
<AggressiveNSECCache
>(10000);
1511 /* insert a NSEC matching the exact name, but it is an ancestor NSEC (delegation) */
1512 DNSName
name("sub.powerdns.com");
1515 rec
.d_type
= QType::NSEC
;
1516 rec
.d_ttl
= now
+ 10;
1518 NSECRecordContent nrc
;
1519 nrc
.d_next
= DNSName("sub1.powerdns.com");
1520 for (const auto& type
: {QType::NS
}) {
1524 rec
.setContent(std::make_shared
<NSECRecordContent
>(nrc
));
1525 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1526 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, false);
1528 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 1U);
1530 /* the cache should now be able to deny the DS */
1531 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::DS
, RCode::NoError
, 3U), true);
1532 /* but not any type that lives in the child zone */
1533 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::AAAA
), false);
1537 cache
= make_unique
<AggressiveNSECCache
>(10000);
1538 /* insert a NSEC matching the exact name inside a zone (neither apex nor delegation point) */
1539 DNSName
name("sub.powerdns.com");
1542 rec
.d_type
= QType::NSEC
;
1543 rec
.d_ttl
= now
+ 10;
1545 NSECRecordContent nrc
;
1546 nrc
.d_next
= DNSName("sub1.powerdns.com");
1547 for (const auto& type
: {QType::A
}) {
1551 rec
.setContent(std::make_shared
<NSECRecordContent
>(nrc
));
1552 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1553 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, false);
1555 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 1U);
1557 /* the cache should now be able to deny other types */
1558 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::AAAA
, RCode::NoError
, 3U), true);
1559 /* including the DS */
1560 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::DS
, RCode::NoError
, 3U), true);
1564 /* nxd inside a zone (neither apex nor delegation point) */
1565 cache
= make_unique
<AggressiveNSECCache
>(10000);
1566 /* insert NSEC proving that the name does not exist */
1567 DNSName
name("sub.powerdns.com.");
1568 DNSName
wc("*.powerdns.com.");
1572 rec
.d_name
= DNSName("sua.powerdns.com");
1573 rec
.d_type
= QType::NSEC
;
1574 rec
.d_ttl
= now
+ 10;
1576 NSECRecordContent nrc
;
1577 nrc
.d_next
= DNSName("suc.powerdns.com");
1578 for (const auto& type
: {QType::A
, QType::SOA
, QType::NS
}) {
1582 rec
.setContent(std::make_shared
<NSECRecordContent
>(nrc
));
1583 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1584 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, false);
1586 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 1U);
1591 rec
.d_name
= DNSName(").powerdns.com.");
1592 rec
.d_type
= QType::NSEC
;
1593 rec
.d_ttl
= now
+ 10;
1595 NSECRecordContent nrc
;
1596 nrc
.d_next
= DNSName("+.powerdns.com.");
1597 for (const auto& type
: {QType::NS
}) {
1601 rec
.setContent(std::make_shared
<NSECRecordContent
>(nrc
));
1602 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1603 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, false);
1605 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 2U);
1608 /* the cache should now be able to deny any type for the name */
1609 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::AAAA
, RCode::NXDomain
, 5U), true);
1611 /* including the DS, since we are not at the apex */
1612 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::DS
, RCode::NXDomain
, 5U), true);
1616 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases
)
1618 AggressiveNSECCache::s_maxNSEC3CommonPrefix
= 159;
1619 auto cache
= make_unique
<AggressiveNSECCache
>(10000);
1620 g_recCache
= std::make_unique
<MemRecursorCache
>();
1622 const DNSName
zone("powerdns.com");
1623 time_t now
= time(nullptr);
1625 /* first we need a SOA */
1626 std::vector
<DNSRecord
> records
;
1627 time_t ttd
= now
+ 30;
1629 drSOA
.d_name
= zone
;
1630 drSOA
.d_type
= QType::SOA
;
1631 drSOA
.d_class
= QClass::IN
;
1632 drSOA
.setContent(std::make_shared
<SOARecordContent
>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
1633 drSOA
.d_ttl
= static_cast<uint32_t>(ttd
); // XXX truncation
1634 drSOA
.d_place
= DNSResourceRecord::ANSWER
;
1635 records
.push_back(drSOA
);
1637 g_recCache
->replace(now
, zone
, QType(QType::SOA
), records
, {}, {}, true, zone
, boost::none
, boost::none
, vState::Secure
);
1638 BOOST_CHECK_EQUAL(g_recCache
->size(), 1U);
1640 const std::string
salt("ab");
1641 const unsigned int iterationsCount
= 1;
1644 cache
= make_unique
<AggressiveNSECCache
>(10000);
1645 /* insert a NSEC3 matching the exact name (apex) */
1646 DNSName
name("sub.powerdns.com");
1647 std::string hashed
= hashQNameWithSalt(salt
, iterationsCount
, name
);
1649 rec
.d_name
= DNSName(toBase32Hex(hashed
)) + zone
;
1650 rec
.d_type
= QType::NSEC3
;
1651 rec
.d_ttl
= now
+ 10;
1653 NSEC3RecordContent nrc
;
1654 nrc
.d_algorithm
= 1;
1656 nrc
.d_iterations
= iterationsCount
;
1658 nrc
.d_nexthash
= hashed
;
1659 incrementHash(nrc
.d_nexthash
);
1660 for (const auto& type
: {QType::A
}) {
1664 rec
.setContent(std::make_shared
<NSEC3RecordContent
>(nrc
));
1665 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 sub.powerdns.com. data");
1666 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, true);
1668 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 1U);
1670 /* the cache should now be able to deny other types (except the DS) */
1671 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::AAAA
, RCode::NoError
, 3U), true);
1672 /* but not the DS that lives in the parent zone */
1673 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::DS
, std::nullopt
, 0U), false);
1677 cache
= make_unique
<AggressiveNSECCache
>(10000);
1678 /* insert a NSEC3 matching the exact name, but it is an ancestor NSEC3 (delegation) */
1679 DNSName
name("sub.powerdns.com");
1680 std::string hashed
= hashQNameWithSalt(salt
, iterationsCount
, name
);
1682 rec
.d_name
= DNSName(toBase32Hex(hashed
)) + zone
;
1683 rec
.d_type
= QType::NSEC3
;
1684 rec
.d_ttl
= now
+ 10;
1686 NSEC3RecordContent nrc
;
1687 nrc
.d_algorithm
= 1;
1689 nrc
.d_iterations
= iterationsCount
;
1691 nrc
.d_nexthash
= hashed
;
1692 incrementHash(nrc
.d_nexthash
);
1693 for (const auto& type
: {QType::NS
}) {
1697 rec
.setContent(std::make_shared
<NSEC3RecordContent
>(nrc
));
1698 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1699 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, true);
1701 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 1U);
1703 /* the cache should now be able to deny the DS */
1704 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::DS
, RCode::NoError
, 3U), true);
1705 /* but not any type that lives in the child zone */
1706 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::AAAA
), false);
1710 cache
= make_unique
<AggressiveNSECCache
>(10000);
1711 /* insert a NSEC3 matching the exact name inside a zone (neither apex nor delegation point) */
1712 DNSName
name("sub.powerdns.com");
1713 std::string hashed
= hashQNameWithSalt(salt
, iterationsCount
, name
);
1715 rec
.d_name
= DNSName(toBase32Hex(hashed
)) + zone
;
1716 rec
.d_type
= QType::NSEC3
;
1717 rec
.d_ttl
= now
+ 10;
1719 NSEC3RecordContent nrc
;
1720 nrc
.d_algorithm
= 1;
1722 nrc
.d_iterations
= iterationsCount
;
1724 nrc
.d_nexthash
= hashed
;
1725 incrementHash(nrc
.d_nexthash
);
1726 for (const auto& type
: {QType::A
}) {
1730 rec
.setContent(std::make_shared
<NSEC3RecordContent
>(nrc
));
1731 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1732 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, true);
1734 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 1U);
1736 /* the cache should now be able to deny other types */
1737 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::AAAA
, RCode::NoError
, 3U), true);
1738 /* including the DS */
1739 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::DS
, RCode::NoError
, 3U), true);
1743 /* nxd inside a zone (neither apex nor delegation point) */
1744 cache
= make_unique
<AggressiveNSECCache
>(10000);
1745 /* insert NSEC3s proving that the name does not exist */
1746 DNSName
name("sub.powerdns.com.");
1747 DNSName
closestEncloser("powerdns.com.");
1748 DNSName
nextCloser("sub.powerdns.com.");
1749 DNSName
wc("*.powerdns.com.");
1752 /* closest encloser */
1753 std::string hashed
= hashQNameWithSalt(salt
, iterationsCount
, closestEncloser
);
1755 rec
.d_name
= DNSName(toBase32Hex(hashed
)) + zone
;
1756 rec
.d_type
= QType::NSEC3
;
1757 rec
.d_ttl
= now
+ 10;
1759 NSEC3RecordContent nrc
;
1760 nrc
.d_algorithm
= 1;
1762 nrc
.d_iterations
= iterationsCount
;
1764 nrc
.d_nexthash
= hashed
;
1765 incrementHash(nrc
.d_nexthash
);
1766 for (const auto& type
: {QType::A
, QType::SOA
, QType::NS
}) {
1770 rec
.setContent(std::make_shared
<NSEC3RecordContent
>(nrc
));
1771 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1772 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, true);
1774 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 1U);
1778 std::string hashed
= hashQNameWithSalt(salt
, iterationsCount
, nextCloser
);
1779 decrementHash(hashed
);
1782 rec
.d_name
= DNSName(toBase32Hex(hashed
)) + zone
;
1783 rec
.d_type
= QType::NSEC3
;
1784 rec
.d_ttl
= now
+ 10;
1786 NSEC3RecordContent nrc
;
1787 nrc
.d_algorithm
= 1;
1789 nrc
.d_iterations
= iterationsCount
;
1791 nrc
.d_nexthash
= hashed
;
1792 incrementHash(nrc
.d_nexthash
);
1793 incrementHash(nrc
.d_nexthash
);
1794 for (const auto& type
: {QType::NS
}) {
1798 rec
.setContent(std::make_shared
<NSEC3RecordContent
>(nrc
));
1799 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1800 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, true);
1802 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 2U);
1806 std::string hashed
= hashQNameWithSalt(salt
, iterationsCount
, wc
);
1807 decrementHash(hashed
);
1810 rec
.d_name
= DNSName(toBase32Hex(hashed
)) + zone
;
1811 rec
.d_type
= QType::NSEC3
;
1812 rec
.d_ttl
= now
+ 10;
1814 NSEC3RecordContent nrc
;
1815 nrc
.d_algorithm
= 1;
1817 nrc
.d_iterations
= iterationsCount
;
1819 nrc
.d_nexthash
= hashed
;
1820 incrementHash(nrc
.d_nexthash
);
1821 incrementHash(nrc
.d_nexthash
);
1822 for (const auto& type
: {QType::NS
}) {
1826 rec
.setContent(std::make_shared
<NSEC3RecordContent
>(nrc
));
1827 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1828 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, true);
1830 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 3U);
1833 /* the cache should now be able to deny any type for the name */
1834 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::AAAA
, RCode::NXDomain
, 7U), true);
1835 /* including the DS, since we are not at the apex */
1836 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::DS
, RCode::NXDomain
, 7U), true);
1839 /* we insert NSEC3s coming from the parent zone that could look like a valid denial but are not */
1840 cache
= make_unique
<AggressiveNSECCache
>(10000);
1842 DNSName
name("www.sub.powerdns.com.");
1843 DNSName
closestEncloser("powerdns.com.");
1844 DNSName
nextCloser("sub.powerdns.com.");
1845 DNSName
wc("*.powerdns.com.");
1848 /* closest encloser */
1849 std::string hashed
= hashQNameWithSalt(salt
, iterationsCount
, closestEncloser
);
1851 rec
.d_name
= DNSName(toBase32Hex(hashed
)) + zone
;
1852 rec
.d_type
= QType::NSEC3
;
1853 rec
.d_ttl
= now
+ 10;
1855 NSEC3RecordContent nrc
;
1856 nrc
.d_algorithm
= 1;
1858 nrc
.d_iterations
= iterationsCount
;
1860 nrc
.d_nexthash
= hashed
;
1861 incrementHash(nrc
.d_nexthash
);
1863 for (const auto& type
: {QType::NS
}) {
1867 rec
.setContent(std::make_shared
<NSEC3RecordContent
>(nrc
));
1868 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1869 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, true);
1871 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 1U);
1875 std::string hashed
= hashQNameWithSalt(salt
, iterationsCount
, nextCloser
);
1876 decrementHash(hashed
);
1879 rec
.d_name
= DNSName(toBase32Hex(hashed
)) + zone
;
1880 rec
.d_type
= QType::NSEC3
;
1881 rec
.d_ttl
= now
+ 10;
1883 NSEC3RecordContent nrc
;
1884 nrc
.d_algorithm
= 1;
1886 nrc
.d_iterations
= iterationsCount
;
1888 nrc
.d_nexthash
= hashed
;
1889 incrementHash(nrc
.d_nexthash
);
1890 incrementHash(nrc
.d_nexthash
);
1891 for (const auto& type
: {QType::A
}) {
1895 rec
.setContent(std::make_shared
<NSEC3RecordContent
>(nrc
));
1896 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1897 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, true);
1899 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 2U);
1903 std::string hashed
= hashQNameWithSalt(salt
, iterationsCount
, wc
);
1904 decrementHash(hashed
);
1907 rec
.d_name
= DNSName(toBase32Hex(hashed
)) + zone
;
1908 rec
.d_type
= QType::NSEC3
;
1909 rec
.d_ttl
= now
+ 10;
1911 NSEC3RecordContent nrc
;
1912 nrc
.d_algorithm
= 1;
1914 nrc
.d_iterations
= iterationsCount
;
1916 nrc
.d_nexthash
= hashed
;
1917 incrementHash(nrc
.d_nexthash
);
1918 incrementHash(nrc
.d_nexthash
);
1919 for (const auto& type
: {QType::A
}) {
1923 rec
.setContent(std::make_shared
<NSEC3RecordContent
>(nrc
));
1924 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1925 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, true);
1927 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 3U);
1930 /* the cache should NOT be able to deny the name */
1931 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::AAAA
, std::nullopt
, 0U), false);
1932 /* and the same for the DS */
1933 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, name
, QType::DS
, std::nullopt
, 0U), false);
1937 BOOST_AUTO_TEST_CASE(test_aggressive_max_nsec3_hash_cost
)
1939 AggressiveNSECCache::s_maxNSEC3CommonPrefix
= 159;
1940 g_recCache
= std::make_unique
<MemRecursorCache
>();
1942 const DNSName
zone("powerdns.com");
1943 time_t now
= time(nullptr);
1945 /* first we need a SOA */
1946 std::vector
<DNSRecord
> records
;
1947 time_t ttd
= now
+ 30;
1949 drSOA
.d_name
= zone
;
1950 drSOA
.d_type
= QType::SOA
;
1951 drSOA
.d_class
= QClass::IN
;
1952 drSOA
.setContent(std::make_shared
<SOARecordContent
>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
1953 drSOA
.d_ttl
= static_cast<uint32_t>(ttd
); // XXX truncation
1954 drSOA
.d_place
= DNSResourceRecord::ANSWER
;
1955 records
.push_back(drSOA
);
1957 g_recCache
->replace(now
, zone
, QType(QType::SOA
), records
, {}, {}, true, zone
, boost::none
, boost::none
, vState::Secure
);
1958 BOOST_CHECK_EQUAL(g_recCache
->size(), 1U);
1960 auto insertNSEC3s
= [zone
, now
](std::unique_ptr
<AggressiveNSECCache
>& cache
, const std::string
& salt
, unsigned int iterationsCount
) -> void {
1962 /* insert a NSEC3 matching the apex (will be the closest encloser) */
1963 DNSName
name("powerdns.com");
1964 std::string hashed
= hashQNameWithSalt(salt
, iterationsCount
, name
);
1966 rec
.d_name
= DNSName(toBase32Hex(hashed
)) + zone
;
1967 rec
.d_type
= QType::NSEC3
;
1968 rec
.d_ttl
= now
+ 10;
1970 NSEC3RecordContent nrc
;
1971 nrc
.d_algorithm
= 1;
1973 nrc
.d_iterations
= iterationsCount
;
1975 nrc
.d_nexthash
= hashed
;
1976 incrementHash(nrc
.d_nexthash
);
1977 for (const auto& type
: {QType::A
}) {
1981 rec
.setContent(std::make_shared
<NSEC3RecordContent
>(nrc
));
1982 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1983 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, true);
1986 /* insert a NSEC3 matching *.powerdns.com (wildcard) */
1987 DNSName
name("*.powerdns.com");
1988 std::string hashed
= hashQNameWithSalt(salt
, iterationsCount
, name
);
1989 auto before
= hashed
;
1990 decrementHash(before
);
1992 rec
.d_name
= DNSName(toBase32Hex(before
)) + zone
;
1993 rec
.d_type
= QType::NSEC3
;
1994 rec
.d_ttl
= now
+ 10;
1996 NSEC3RecordContent nrc
;
1997 nrc
.d_algorithm
= 1;
1999 nrc
.d_iterations
= iterationsCount
;
2001 nrc
.d_nexthash
= hashed
;
2002 incrementHash(nrc
.d_nexthash
);
2003 for (const auto& type
: {QType::A
}) {
2007 rec
.setContent(std::make_shared
<NSEC3RecordContent
>(nrc
));
2008 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
2009 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, true);
2012 /* insert a NSEC3 matching sub.powerdns.com (next closer) */
2013 DNSName
name("sub.powerdns.com");
2014 std::string hashed
= hashQNameWithSalt(salt
, iterationsCount
, name
);
2015 auto before
= hashed
;
2016 decrementHash(before
);
2018 rec
.d_name
= DNSName(toBase32Hex(before
)) + zone
;
2019 rec
.d_type
= QType::NSEC3
;
2020 rec
.d_ttl
= now
+ 10;
2022 NSEC3RecordContent nrc
;
2023 nrc
.d_algorithm
= 1;
2025 nrc
.d_iterations
= iterationsCount
;
2027 nrc
.d_nexthash
= hashed
;
2028 incrementHash(nrc
.d_nexthash
);
2029 for (const auto& type
: {QType::A
}) {
2033 rec
.setContent(std::make_shared
<NSEC3RecordContent
>(nrc
));
2034 auto rrsig
= std::make_shared
<RRSIGRecordContent
>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
2035 cache
->insertNSEC(zone
, rec
.d_name
, rec
, {rrsig
}, true);
2037 BOOST_CHECK_EQUAL(cache
->getEntriesCount(), 3U);
2041 /* zone with cheap parameters */
2042 const std::string salt
;
2043 const unsigned int iterationsCount
= 0;
2044 AggressiveNSECCache::s_nsec3DenialProofMaxCost
= 10;
2046 auto cache
= make_unique
<AggressiveNSECCache
>(10000);
2047 insertNSEC3s(cache
, salt
, iterationsCount
);
2049 /* the cache should now be able to deny everything below sub.powerdns.com,
2050 IF IT DOES NOT EXCEED THE COST */
2052 /* short name: 10 labels below the zone apex */
2053 DNSName
lookupName("a.b.c.d.e.f.g.h.i.sub.powerdns.com.");
2054 BOOST_CHECK_EQUAL(lookupName
.countLabels() - zone
.countLabels(), 10U);
2055 BOOST_CHECK_LE(getNSEC3DenialProofWorstCaseIterationsCount(lookupName
.countLabels() - zone
.countLabels(), iterationsCount
, salt
.size()), AggressiveNSECCache::s_nsec3DenialProofMaxCost
);
2056 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, lookupName
, QType::AAAA
, RCode::NXDomain
, 7U), true);
2059 /* longer name: 11 labels below the zone apex */
2060 DNSName
lookupName("a.b.c.d.e.f.g.h.i.j.sub.powerdns.com.");
2061 BOOST_CHECK_EQUAL(lookupName
.countLabels() - zone
.countLabels(), 11U);
2062 BOOST_CHECK_GT(getNSEC3DenialProofWorstCaseIterationsCount(lookupName
.countLabels() - zone
.countLabels(), iterationsCount
, salt
.size()), AggressiveNSECCache::s_nsec3DenialProofMaxCost
);
2063 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, lookupName
, QType::AAAA
), false);
2068 /* zone with expensive parameters */
2069 const std::string
salt("deadbeef");
2070 const unsigned int iterationsCount
= 50;
2071 AggressiveNSECCache::s_nsec3DenialProofMaxCost
= 100;
2073 auto cache
= make_unique
<AggressiveNSECCache
>(10000);
2074 insertNSEC3s(cache
, salt
, iterationsCount
);
2076 /* the cache should now be able to deny everything below sub.powerdns.com,
2077 IF IT DOES NOT EXCEED THE COST */
2079 /* short name: 1 label below the zone apex */
2080 DNSName
lookupName("sub.powerdns.com.");
2081 BOOST_CHECK_EQUAL(lookupName
.countLabels() - zone
.countLabels(), 1U);
2082 BOOST_CHECK_LE(getNSEC3DenialProofWorstCaseIterationsCount(lookupName
.countLabels() - zone
.countLabels(), iterationsCount
, salt
.size()), AggressiveNSECCache::s_nsec3DenialProofMaxCost
);
2083 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, lookupName
, QType::AAAA
, RCode::NXDomain
, 7U), true);
2086 /* longer name: 2 labels below the zone apex */
2087 DNSName
lookupName("a.sub.powerdns.com.");
2088 BOOST_CHECK_EQUAL(lookupName
.countLabels() - zone
.countLabels(), 2U);
2089 BOOST_CHECK_GT(getNSEC3DenialProofWorstCaseIterationsCount(lookupName
.countLabels() - zone
.countLabels(), iterationsCount
, salt
.size()), AggressiveNSECCache::s_nsec3DenialProofMaxCost
);
2090 BOOST_CHECK_EQUAL(getDenialWrapper(cache
, now
, lookupName
, QType::AAAA
), false);
2095 BOOST_AUTO_TEST_SUITE_END()