]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/recursordist/test-aggressive_nsec_cc.cc
rec: CVE-2023-50387 and CVE-2023-50868
[thirdparty/pdns.git] / pdns / recursordist / test-aggressive_nsec_cc.cc
1 #ifndef BOOST_TEST_DYN_LINK
2 #define BOOST_TEST_DYN_LINK
3 #endif
4
5 #include <boost/test/unit_test.hpp>
6
7 #include "aggressive_nsec.hh"
8 #include "test-syncres_cc.hh"
9
10 BOOST_AUTO_TEST_SUITE(aggressive_nsec_cc)
11
12 BOOST_AUTO_TEST_CASE(test_small_coverering_nsec3)
13 {
14 AggressiveNSECCache::s_maxNSEC3CommonPrefix = 1;
15
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},
27 };
28
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);
32 }
33 }
34
35 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nxdomain)
36 {
37 std::unique_ptr<SyncRes> sr;
38 initSR(sr, true);
39 g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
40
41 setDNSSECValidation(sr, DNSSECMode::ValidateAll);
42
43 primeHints();
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
46 either */
47 const DNSName target1("b.powerdns.com.");
48 const DNSName target2("g.powerdns.com.");
49 testkeysset_t keys;
50
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);
55
56 g_luaconfs.setState(luaconfsCopy);
57
58 size_t queriesCount = 0;
59
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 */) {
61 queriesCount++;
62
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);
67 /* no data */
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;
77 }
78 else if (domain == DNSName("com.")) {
79 /* no cut */
80 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
81 }
82 else if (domain == DNSName("powerdns.com.")) {
83 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
84 }
85 else {
86 /* cut */
87 return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
88 }
89 }
90 else {
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;
98 }
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;
111 }
112 }
113 }
114
115 return LWResult::Result::Timeout;
116 });
117
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);
124
125 ret.clear();
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);
131 }
132
133 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nodata)
134 {
135 std::unique_ptr<SyncRes> sr;
136 initSR(sr, true);
137 g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
138
139 setDNSSECValidation(sr, DNSSECMode::ValidateAll);
140
141 primeHints();
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.");
145 testkeysset_t keys;
146
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);
151
152 g_luaconfs.setState(luaconfsCopy);
153
154 size_t queriesCount = 0;
155
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 */) {
157 queriesCount++;
158
159 if (type == QType::DS || type == QType::DNSKEY) {
160 if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) {
161 /* no cut, NSEC */
162 return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
163 }
164 else if (domain == DNSName("com.")) {
165 /* no cut */
166 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
167 }
168 else if (domain == DNSName("powerdns.com.")) {
169 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
170 }
171 else {
172 /* cut */
173 return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
174 }
175 }
176 else {
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;
184 }
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);
188 /* no data */
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 */
192 /* exact match */
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;
197 }
198 }
199 }
200
201 return LWResult::Result::Timeout;
202 });
203
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);
210
211 ret.clear();
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);
217 }
218
219 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nodata_wildcard)
220 {
221 std::unique_ptr<SyncRes> sr;
222 initSR(sr, true);
223 g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
224
225 setDNSSECValidation(sr, DNSSECMode::ValidateAll);
226
227 primeHints();
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.");
231 testkeysset_t keys;
232
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);
237
238 g_luaconfs.setState(luaconfsCopy);
239
240 size_t queriesCount = 0;
241
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 */) {
243 queriesCount++;
244
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);
249 /* no data */
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;
256 }
257 else if (domain == DNSName("com.")) {
258 /* no cut */
259 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
260 }
261 else if (domain == DNSName("powerdns.com.")) {
262 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
263 }
264 else {
265 /* cut */
266 return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
267 }
268 }
269 else {
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;
277 }
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);
281 /* no data */
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;
288 }
289 }
290 }
291
292 return LWResult::Result::Timeout;
293 });
294
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);
301
302 ret.clear();
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);
308 }
309
310 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor)
311 {
312 std::unique_ptr<SyncRes> sr;
313 initSR(sr, true);
314 g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
315
316 setDNSSECValidation(sr, DNSSECMode::ValidateAll);
317
318 primeHints();
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.");
324 testkeysset_t keys;
325
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);
330
331 g_luaconfs.setState(luaconfsCopy);
332
333 size_t queriesCount = 0;
334
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 */) {
336 queriesCount++;
337
338 if (type == QType::DS || type == QType::DNSKEY) {
339 if (domain == DNSName("com.")) {
340 /* no cut */
341 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
342 }
343 else {
344 /* cut */
345 return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
346 }
347 }
348 else {
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;
356 }
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;
367 }
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
376 */
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;
380 }
381 }
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;
387 }
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;
392 }
393 }
394 }
395
396 return LWResult::Result::Timeout;
397 });
398
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);
405
406 /* now we query sub16.powerdns.com, to get a NSEC covering the wildcard for *.sub.powerdns.com */
407 ret.clear();
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);
413
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 */
416 ret.clear();
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);
422 }
423
424 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_wildcard_synthesis)
425 {
426 std::unique_ptr<SyncRes> sr;
427 initSR(sr, true);
428 g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
429
430 setDNSSECValidation(sr, DNSSECMode::ValidateAll);
431
432 primeHints();
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.");
438 testkeysset_t keys;
439
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);
444
445 g_luaconfs.setState(luaconfsCopy);
446
447 size_t queriesCount = 0;
448
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 */) {
450 queriesCount++;
451
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);
456 /* no data */
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;
463 }
464 else if (domain == DNSName("com.")) {
465 /* no cut */
466 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
467 }
468 else if (domain == DNSName("powerdns.com.")) {
469 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
470 }
471 else {
472 /* cut */
473 return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
474 }
475 }
476 else {
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;
484 }
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;
494 }
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;
503 }
504 }
505 }
506
507 return LWResult::Result::Timeout;
508 });
509
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);
518
519 /* request the TXT to get the SOA */
520 ret.clear();
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);
528
529 ret.clear();
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);
537 }
538
539 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nxdomain)
540 {
541 std::unique_ptr<SyncRes> sr;
542 initSR(sr, true);
543 AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159;
544 g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
545
546 setDNSSECValidation(sr, DNSSECMode::ValidateAll);
547
548 primeHints();
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
552 either */
553 const DNSName target1("b.powerdns.com.");
554 const DNSName target2("g.powerdns.com.");
555 testkeysset_t keys;
556
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);
561
562 g_luaconfs.setState(luaconfsCopy);
563
564 size_t queriesCount = 0;
565
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 */) {
567 queriesCount++;
568
569 if (type == QType::DS || type == QType::DNSKEY) {
570 if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) {
571 /* no cut, NSEC3 */
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);
582 /* no wildcard */
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;
586 }
587 else if (domain == DNSName("com.")) {
588 /* no cut */
589 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
590 }
591 else if (domain == DNSName("powerdns.com.")) {
592 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
593 }
594 else {
595 /* cut */
596 return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
597 }
598 }
599 else {
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;
607 }
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);
620 /* no wildcard */
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;
624 }
625 }
626 }
627
628 return LWResult::Result::Timeout;
629 });
630
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);
637
638 ret.clear();
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);
644 }
645
646 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata)
647 {
648 std::unique_ptr<SyncRes> sr;
649 initSR(sr, true);
650 g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
651
652 setDNSSECValidation(sr, DNSSECMode::ValidateAll);
653
654 primeHints();
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.");
658 testkeysset_t keys;
659
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);
664
665 g_luaconfs.setState(luaconfsCopy);
666
667 size_t queriesCount = 0;
668
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 */) {
670 queriesCount++;
671
672 if (type == QType::DS || type == QType::DNSKEY) {
673 if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) {
674 /* no cut, NSEC3 */
675 return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false, boost::none, true);
676 }
677 else if (domain == DNSName("com.")) {
678 /* no cut */
679 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
680 }
681 else if (domain == DNSName("powerdns.com.")) {
682 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
683 }
684 else {
685 /* cut */
686 return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
687 }
688 }
689 else {
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;
697 }
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);
701 /* no data */
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 */
705 /* exact match */
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;
710 }
711 }
712 }
713
714 return LWResult::Result::Timeout;
715 });
716
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);
723
724 ret.clear();
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);
730 }
731
732 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata_wildcard)
733 {
734 std::unique_ptr<SyncRes> sr;
735 initSR(sr, true);
736 AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159;
737 g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
738
739 setDNSSECValidation(sr, DNSSECMode::ValidateAll);
740
741 primeHints();
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.");
745 testkeysset_t keys;
746
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);
751
752 g_luaconfs.setState(luaconfsCopy);
753
754 size_t queriesCount = 0;
755
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 */) {
757 queriesCount++;
758
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);
763 /* no data */
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;
776 }
777 else if (domain == DNSName("com.")) {
778 /* no cut */
779 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
780 }
781 else if (domain == DNSName("powerdns.com.")) {
782 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
783 }
784 else {
785 /* cut */
786 return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
787 }
788 }
789 else {
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;
797 }
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);
801 /* no data */
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;
814 }
815 }
816 }
817
818 return LWResult::Result::Timeout;
819 });
820
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);
827
828 ret.clear();
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);
834 }
835
836 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor)
837 {
838 std::unique_ptr<SyncRes> sr;
839 initSR(sr, true);
840 g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
841
842 setDNSSECValidation(sr, DNSSECMode::ValidateAll);
843
844 primeHints();
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.");
850 testkeysset_t keys;
851
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);
856
857 g_luaconfs.setState(luaconfsCopy);
858
859 size_t queriesCount = 0;
860
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 */) {
862 queriesCount++;
863
864 if (type == QType::DS || type == QType::DNSKEY) {
865 if (domain == DNSName("com.")) {
866 /* no cut */
867 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
868 }
869 else {
870 /* cut */
871 return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
872 }
873 }
874 else {
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;
882 }
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;
894 }
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)
907 */
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;
911 }
912 }
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;
918 }
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;
923 }
924 }
925 }
926
927 return LWResult::Result::Timeout;
928 });
929
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);
936
937 /* now we query sub16.powerdns.com, to get a hash covering the wildcard for
938 *.sub.powerdns.com */
939 ret.clear();
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);
945
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 */
948 ret.clear();
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);
954 }
955
956 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_wildcard_synthesis)
957 {
958 std::unique_ptr<SyncRes> sr;
959 initSR(sr, true);
960 g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
961
962 setDNSSECValidation(sr, DNSSECMode::ValidateAll);
963
964 primeHints();
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.");
970 testkeysset_t keys;
971
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);
976
977 g_luaconfs.setState(luaconfsCopy);
978
979 size_t queriesCount = 0;
980
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 */) {
982 queriesCount++;
983
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);
988 /* no data */
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;
1001 }
1002 else if (domain == DNSName("com.")) {
1003 /* no cut */
1004 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
1005 }
1006 else if (domain == DNSName("powerdns.com.")) {
1007 return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
1008 }
1009 else {
1010 /* cut */
1011 return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
1012 }
1013 }
1014 else {
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;
1022 }
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;
1034 }
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;
1050 }
1051 }
1052 }
1053
1054 return LWResult::Result::Timeout;
1055 });
1056
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);
1065
1066 ret.clear();
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);
1074
1075 ret.clear();
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);
1083 }
1084
1085 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_replace)
1086 {
1087 const size_t testSize = 10000;
1088 auto cache = make_unique<AggressiveNSECCache>(testSize);
1089
1090 struct timeval now
1091 {
1092 };
1093 Utility::gettimeofday(&now, nullptr);
1094
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");
1099 }
1100
1101 DTime time;
1102 time.set();
1103
1104 for (const auto& name : names) {
1105 DNSRecord rec;
1106 rec.d_name = name;
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);
1112 }
1113 auto diff1 = time.udiff(true);
1114
1115 BOOST_CHECK_EQUAL(cache->getEntriesCount(), testSize);
1116 for (const auto& name : names) {
1117 DNSRecord rec;
1118 rec.d_name = name;
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);
1124 }
1125
1126 BOOST_CHECK_EQUAL(cache->getEntriesCount(), testSize);
1127
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);
1131 }
1132
1133 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_wiping)
1134 {
1135 auto cache = make_unique<AggressiveNSECCache>(10000);
1136
1137 struct timeval now;
1138 Utility::gettimeofday(&now, 0);
1139
1140 DNSRecord rec;
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);
1147
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);
1151
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);
1158
1159 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
1160
1161 /* remove just that zone */
1162 cache->removeZoneInfo(DNSName("powerdns.org"), false);
1163 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U);
1164
1165 /* add it back */
1166 cache->insertNSEC(DNSName("powerdns.org"), rec.d_name, rec, {rrsig}, true);
1167 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
1168
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);
1172
1173 /* add it back */
1174 cache->insertNSEC(DNSName("powerdns.org"), rec.d_name, rec, {rrsig}, true);
1175 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
1176
1177 /* remove everything */
1178 cache->removeZoneInfo(DNSName("."), true);
1179 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 0U);
1180 }
1181
1182 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_pruning)
1183 {
1184 auto cache = make_unique<AggressiveNSECCache>(2);
1185
1186 struct timeval now;
1187 Utility::gettimeofday(&now, 0);
1188
1189 DNSRecord rec;
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);
1196
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);
1200
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);
1206
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);
1213
1214 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
1215
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);
1221
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);
1226 }
1227
1228 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_dump)
1229 {
1230 auto cache = make_unique<AggressiveNSECCache>(10000);
1231
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");
1241
1242 struct timeval now
1243 {
1244 };
1245 Utility::gettimeofday(&now, nullptr);
1246
1247 DNSRecord rec;
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);
1254
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);
1258
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);
1265
1266 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
1267
1268 auto filePtr = std::unique_ptr<FILE, int (*)(FILE*)>(tmpfile(), fclose);
1269 if (!filePtr) {
1270 BOOST_FAIL("Temporary file could not be opened");
1271 }
1272
1273 BOOST_CHECK_EQUAL(cache->dumpToFile(filePtr, now), 3U);
1274
1275 rewind(filePtr.get());
1276 char* line = nullptr;
1277 size_t len = 0;
1278
1279 for (const auto& str : expected) {
1280 auto read = getline(&line, &len, filePtr.get());
1281 if (read == -1) {
1282 BOOST_FAIL("Unable to read a line from the temp file");
1283 }
1284 BOOST_CHECK_EQUAL(line, str);
1285 }
1286
1287 expected.clear();
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");
1296
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);
1303
1304 rewind(filePtr.get());
1305 BOOST_CHECK_EQUAL(cache->dumpToFile(filePtr, now), 3U);
1306
1307 rewind(filePtr.get());
1308
1309 for (const auto& str : expected) {
1310 auto read = getline(&line, &len, filePtr.get());
1311 if (read == -1) {
1312 BOOST_FAIL("Unable to read a line from the temp file");
1313 }
1314 BOOST_CHECK_EQUAL(line, str);
1315 }
1316
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.
1321 }
1322
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)
1324 {
1325 int res;
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);
1332 }
1333 if (expectedRecordsCount) {
1334 BOOST_CHECK_EQUAL(results.size(), *expectedRecordsCount);
1335 }
1336 return found;
1337 }
1338
1339 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_rollover)
1340 {
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>();
1346
1347 const DNSName zone("powerdns.com");
1348 time_t now = time(nullptr);
1349
1350 /* first we need a SOA */
1351 std::vector<DNSRecord> records;
1352 time_t ttd = now + 30;
1353 DNSRecord drSOA;
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);
1361
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);
1364
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);
1371
1372 DNSRecord rec;
1373 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1374 rec.d_type = QType::NSEC3;
1375 rec.d_ttl = now + 10;
1376
1377 NSEC3RecordContent nrc;
1378 nrc.d_algorithm = 1;
1379 nrc.d_flags = 0;
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}) {
1385 nrc.set(type);
1386 }
1387
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);
1391
1392 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1393
1394 /* we can use the NSEC3s we have */
1395 /* direct match */
1396 BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA), true);
1397
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;
1405 nrc.d_flags = 0;
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}) {
1411 nrc.set(type);
1412 }
1413
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);
1417
1418 /* the existing entries should have been cleared */
1419 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1420
1421 /* we should be able to find a direct match for that name */
1422 /* direct match */
1423 BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, other, QType::AAAA), true);
1424
1425 /* but we should not be able to use the other NSEC3s */
1426 BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA), false);
1427
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;
1435 nrc.d_flags = 0;
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}) {
1441 nrc.set(type);
1442 }
1443
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);
1447
1448 /* the existing entries should have been cleared */
1449 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1450
1451 /* we should be able to find a direct match for that name */
1452 /* direct match */
1453 BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, other2, QType::AAAA), true);
1454
1455 /* but we should not be able to use the other NSEC3s */
1456 BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, other, QType::AAAA), false);
1457 }
1458
1459 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor_cases)
1460 {
1461 auto cache = make_unique<AggressiveNSECCache>(10000);
1462 g_recCache = std::make_unique<MemRecursorCache>();
1463
1464 const DNSName zone("powerdns.com");
1465 time_t now = time(nullptr);
1466
1467 /* first we need a SOA */
1468 std::vector<DNSRecord> records;
1469 time_t ttd = now + 30;
1470 DNSRecord drSOA;
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);
1478
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);
1481
1482 {
1483 cache = make_unique<AggressiveNSECCache>(10000);
1484 /* insert a NSEC matching the exact name (apex) */
1485 DNSName name("sub.powerdns.com");
1486 DNSRecord rec;
1487 rec.d_name = name;
1488 rec.d_type = QType::NSEC;
1489 rec.d_ttl = now + 10;
1490
1491 NSECRecordContent nrc;
1492 nrc.d_next = DNSName("sub1.powerdns.com");
1493 for (const auto& type : {QType::A}) {
1494 nrc.set(type);
1495 }
1496
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);
1500
1501 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1502
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);
1507 }
1508
1509 {
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");
1513 DNSRecord rec;
1514 rec.d_name = name;
1515 rec.d_type = QType::NSEC;
1516 rec.d_ttl = now + 10;
1517
1518 NSECRecordContent nrc;
1519 nrc.d_next = DNSName("sub1.powerdns.com");
1520 for (const auto& type : {QType::NS}) {
1521 nrc.set(type);
1522 }
1523
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);
1527
1528 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1529
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);
1534 }
1535
1536 {
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");
1540 DNSRecord rec;
1541 rec.d_name = name;
1542 rec.d_type = QType::NSEC;
1543 rec.d_ttl = now + 10;
1544
1545 NSECRecordContent nrc;
1546 nrc.d_next = DNSName("sub1.powerdns.com");
1547 for (const auto& type : {QType::A}) {
1548 nrc.set(type);
1549 }
1550
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);
1554
1555 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1556
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);
1561 }
1562
1563 {
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.");
1569
1570 {
1571 DNSRecord rec;
1572 rec.d_name = DNSName("sua.powerdns.com");
1573 rec.d_type = QType::NSEC;
1574 rec.d_ttl = now + 10;
1575
1576 NSECRecordContent nrc;
1577 nrc.d_next = DNSName("suc.powerdns.com");
1578 for (const auto& type : {QType::A, QType::SOA, QType::NS}) {
1579 nrc.set(type);
1580 }
1581
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);
1585
1586 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1587 }
1588 {
1589 /* wildcard */
1590 DNSRecord rec;
1591 rec.d_name = DNSName(").powerdns.com.");
1592 rec.d_type = QType::NSEC;
1593 rec.d_ttl = now + 10;
1594
1595 NSECRecordContent nrc;
1596 nrc.d_next = DNSName("+.powerdns.com.");
1597 for (const auto& type : {QType::NS}) {
1598 nrc.set(type);
1599 }
1600
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);
1604
1605 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U);
1606 }
1607
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);
1610
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);
1613 }
1614 }
1615
1616 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
1617 {
1618 AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159;
1619 auto cache = make_unique<AggressiveNSECCache>(10000);
1620 g_recCache = std::make_unique<MemRecursorCache>();
1621
1622 const DNSName zone("powerdns.com");
1623 time_t now = time(nullptr);
1624
1625 /* first we need a SOA */
1626 std::vector<DNSRecord> records;
1627 time_t ttd = now + 30;
1628 DNSRecord drSOA;
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);
1636
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);
1639
1640 const std::string salt("ab");
1641 const unsigned int iterationsCount = 1;
1642
1643 {
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);
1648 DNSRecord rec;
1649 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1650 rec.d_type = QType::NSEC3;
1651 rec.d_ttl = now + 10;
1652
1653 NSEC3RecordContent nrc;
1654 nrc.d_algorithm = 1;
1655 nrc.d_flags = 0;
1656 nrc.d_iterations = iterationsCount;
1657 nrc.d_salt = salt;
1658 nrc.d_nexthash = hashed;
1659 incrementHash(nrc.d_nexthash);
1660 for (const auto& type : {QType::A}) {
1661 nrc.set(type);
1662 }
1663
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);
1667
1668 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1669
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);
1674 }
1675
1676 {
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);
1681 DNSRecord rec;
1682 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1683 rec.d_type = QType::NSEC3;
1684 rec.d_ttl = now + 10;
1685
1686 NSEC3RecordContent nrc;
1687 nrc.d_algorithm = 1;
1688 nrc.d_flags = 0;
1689 nrc.d_iterations = iterationsCount;
1690 nrc.d_salt = salt;
1691 nrc.d_nexthash = hashed;
1692 incrementHash(nrc.d_nexthash);
1693 for (const auto& type : {QType::NS}) {
1694 nrc.set(type);
1695 }
1696
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);
1700
1701 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1702
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);
1707 }
1708
1709 {
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);
1714 DNSRecord rec;
1715 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1716 rec.d_type = QType::NSEC3;
1717 rec.d_ttl = now + 10;
1718
1719 NSEC3RecordContent nrc;
1720 nrc.d_algorithm = 1;
1721 nrc.d_flags = 0;
1722 nrc.d_iterations = iterationsCount;
1723 nrc.d_salt = salt;
1724 nrc.d_nexthash = hashed;
1725 incrementHash(nrc.d_nexthash);
1726 for (const auto& type : {QType::A}) {
1727 nrc.set(type);
1728 }
1729
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);
1733
1734 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1735
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);
1740 }
1741
1742 {
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.");
1750
1751 {
1752 /* closest encloser */
1753 std::string hashed = hashQNameWithSalt(salt, iterationsCount, closestEncloser);
1754 DNSRecord rec;
1755 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1756 rec.d_type = QType::NSEC3;
1757 rec.d_ttl = now + 10;
1758
1759 NSEC3RecordContent nrc;
1760 nrc.d_algorithm = 1;
1761 nrc.d_flags = 0;
1762 nrc.d_iterations = iterationsCount;
1763 nrc.d_salt = salt;
1764 nrc.d_nexthash = hashed;
1765 incrementHash(nrc.d_nexthash);
1766 for (const auto& type : {QType::A, QType::SOA, QType::NS}) {
1767 nrc.set(type);
1768 }
1769
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);
1773
1774 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1775 }
1776 {
1777 /* next closer */
1778 std::string hashed = hashQNameWithSalt(salt, iterationsCount, nextCloser);
1779 decrementHash(hashed);
1780
1781 DNSRecord rec;
1782 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1783 rec.d_type = QType::NSEC3;
1784 rec.d_ttl = now + 10;
1785
1786 NSEC3RecordContent nrc;
1787 nrc.d_algorithm = 1;
1788 nrc.d_flags = 0;
1789 nrc.d_iterations = iterationsCount;
1790 nrc.d_salt = salt;
1791 nrc.d_nexthash = hashed;
1792 incrementHash(nrc.d_nexthash);
1793 incrementHash(nrc.d_nexthash);
1794 for (const auto& type : {QType::NS}) {
1795 nrc.set(type);
1796 }
1797
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);
1801
1802 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U);
1803 }
1804 {
1805 /* wildcard */
1806 std::string hashed = hashQNameWithSalt(salt, iterationsCount, wc);
1807 decrementHash(hashed);
1808
1809 DNSRecord rec;
1810 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1811 rec.d_type = QType::NSEC3;
1812 rec.d_ttl = now + 10;
1813
1814 NSEC3RecordContent nrc;
1815 nrc.d_algorithm = 1;
1816 nrc.d_flags = 0;
1817 nrc.d_iterations = iterationsCount;
1818 nrc.d_salt = salt;
1819 nrc.d_nexthash = hashed;
1820 incrementHash(nrc.d_nexthash);
1821 incrementHash(nrc.d_nexthash);
1822 for (const auto& type : {QType::NS}) {
1823 nrc.set(type);
1824 }
1825
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);
1829
1830 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
1831 }
1832
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);
1837 }
1838 {
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);
1841
1842 DNSName name("www.sub.powerdns.com.");
1843 DNSName closestEncloser("powerdns.com.");
1844 DNSName nextCloser("sub.powerdns.com.");
1845 DNSName wc("*.powerdns.com.");
1846
1847 {
1848 /* closest encloser */
1849 std::string hashed = hashQNameWithSalt(salt, iterationsCount, closestEncloser);
1850 DNSRecord rec;
1851 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1852 rec.d_type = QType::NSEC3;
1853 rec.d_ttl = now + 10;
1854
1855 NSEC3RecordContent nrc;
1856 nrc.d_algorithm = 1;
1857 nrc.d_flags = 0;
1858 nrc.d_iterations = iterationsCount;
1859 nrc.d_salt = salt;
1860 nrc.d_nexthash = hashed;
1861 incrementHash(nrc.d_nexthash);
1862 /* delegation ! */
1863 for (const auto& type : {QType::NS}) {
1864 nrc.set(type);
1865 }
1866
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);
1870
1871 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1872 }
1873 {
1874 /* next closer */
1875 std::string hashed = hashQNameWithSalt(salt, iterationsCount, nextCloser);
1876 decrementHash(hashed);
1877
1878 DNSRecord rec;
1879 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1880 rec.d_type = QType::NSEC3;
1881 rec.d_ttl = now + 10;
1882
1883 NSEC3RecordContent nrc;
1884 nrc.d_algorithm = 1;
1885 nrc.d_flags = 0;
1886 nrc.d_iterations = iterationsCount;
1887 nrc.d_salt = salt;
1888 nrc.d_nexthash = hashed;
1889 incrementHash(nrc.d_nexthash);
1890 incrementHash(nrc.d_nexthash);
1891 for (const auto& type : {QType::A}) {
1892 nrc.set(type);
1893 }
1894
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);
1898
1899 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U);
1900 }
1901 {
1902 /* wildcard */
1903 std::string hashed = hashQNameWithSalt(salt, iterationsCount, wc);
1904 decrementHash(hashed);
1905
1906 DNSRecord rec;
1907 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1908 rec.d_type = QType::NSEC3;
1909 rec.d_ttl = now + 10;
1910
1911 NSEC3RecordContent nrc;
1912 nrc.d_algorithm = 1;
1913 nrc.d_flags = 0;
1914 nrc.d_iterations = iterationsCount;
1915 nrc.d_salt = salt;
1916 nrc.d_nexthash = hashed;
1917 incrementHash(nrc.d_nexthash);
1918 incrementHash(nrc.d_nexthash);
1919 for (const auto& type : {QType::A}) {
1920 nrc.set(type);
1921 }
1922
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);
1926
1927 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
1928 }
1929
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);
1934 }
1935 }
1936
1937 BOOST_AUTO_TEST_CASE(test_aggressive_max_nsec3_hash_cost)
1938 {
1939 AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159;
1940 g_recCache = std::make_unique<MemRecursorCache>();
1941
1942 const DNSName zone("powerdns.com");
1943 time_t now = time(nullptr);
1944
1945 /* first we need a SOA */
1946 std::vector<DNSRecord> records;
1947 time_t ttd = now + 30;
1948 DNSRecord drSOA;
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);
1956
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);
1959
1960 auto insertNSEC3s = [zone, now](std::unique_ptr<AggressiveNSECCache>& cache, const std::string& salt, unsigned int iterationsCount) -> void {
1961 {
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);
1965 DNSRecord rec;
1966 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1967 rec.d_type = QType::NSEC3;
1968 rec.d_ttl = now + 10;
1969
1970 NSEC3RecordContent nrc;
1971 nrc.d_algorithm = 1;
1972 nrc.d_flags = 0;
1973 nrc.d_iterations = iterationsCount;
1974 nrc.d_salt = salt;
1975 nrc.d_nexthash = hashed;
1976 incrementHash(nrc.d_nexthash);
1977 for (const auto& type : {QType::A}) {
1978 nrc.set(type);
1979 }
1980
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);
1984 }
1985 {
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);
1991 DNSRecord rec;
1992 rec.d_name = DNSName(toBase32Hex(before)) + zone;
1993 rec.d_type = QType::NSEC3;
1994 rec.d_ttl = now + 10;
1995
1996 NSEC3RecordContent nrc;
1997 nrc.d_algorithm = 1;
1998 nrc.d_flags = 0;
1999 nrc.d_iterations = iterationsCount;
2000 nrc.d_salt = salt;
2001 nrc.d_nexthash = hashed;
2002 incrementHash(nrc.d_nexthash);
2003 for (const auto& type : {QType::A}) {
2004 nrc.set(type);
2005 }
2006
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);
2010 }
2011 {
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);
2017 DNSRecord rec;
2018 rec.d_name = DNSName(toBase32Hex(before)) + zone;
2019 rec.d_type = QType::NSEC3;
2020 rec.d_ttl = now + 10;
2021
2022 NSEC3RecordContent nrc;
2023 nrc.d_algorithm = 1;
2024 nrc.d_flags = 0;
2025 nrc.d_iterations = iterationsCount;
2026 nrc.d_salt = salt;
2027 nrc.d_nexthash = hashed;
2028 incrementHash(nrc.d_nexthash);
2029 for (const auto& type : {QType::A}) {
2030 nrc.set(type);
2031 }
2032
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);
2036 }
2037 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
2038 };
2039
2040 {
2041 /* zone with cheap parameters */
2042 const std::string salt;
2043 const unsigned int iterationsCount = 0;
2044 AggressiveNSECCache::s_nsec3DenialProofMaxCost = 10;
2045
2046 auto cache = make_unique<AggressiveNSECCache>(10000);
2047 insertNSEC3s(cache, salt, iterationsCount);
2048
2049 /* the cache should now be able to deny everything below sub.powerdns.com,
2050 IF IT DOES NOT EXCEED THE COST */
2051 {
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);
2057 }
2058 {
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);
2064 }
2065 }
2066
2067 {
2068 /* zone with expensive parameters */
2069 const std::string salt("deadbeef");
2070 const unsigned int iterationsCount = 50;
2071 AggressiveNSECCache::s_nsec3DenialProofMaxCost = 100;
2072
2073 auto cache = make_unique<AggressiveNSECCache>(10000);
2074 insertNSEC3s(cache, salt, iterationsCount);
2075
2076 /* the cache should now be able to deny everything below sub.powerdns.com,
2077 IF IT DOES NOT EXCEED THE COST */
2078 {
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);
2084 }
2085 {
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);
2091 }
2092 }
2093 }
2094
2095 BOOST_AUTO_TEST_SUITE_END()