]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/recursordist/test-aggressive_nsec_cc.cc
fcc3a81fdadb09457bb944aae417c9f6ee589bf2
[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 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_rollover)
1324 {
1325 /* test that we don't compare a hash using the wrong (former) salt or iterations count in case of a rollover,
1326 or when different servers use different parameters */
1327 AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159;
1328 auto cache = make_unique<AggressiveNSECCache>(10000);
1329 g_recCache = std::make_unique<MemRecursorCache>();
1330
1331 const DNSName zone("powerdns.com");
1332 time_t now = time(nullptr);
1333
1334 /* first we need a SOA */
1335 std::vector<DNSRecord> records;
1336 time_t ttd = now + 30;
1337 DNSRecord drSOA;
1338 drSOA.d_name = zone;
1339 drSOA.d_type = QType::SOA;
1340 drSOA.d_class = QClass::IN;
1341 drSOA.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
1342 drSOA.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation
1343 drSOA.d_place = DNSResourceRecord::ANSWER;
1344 records.push_back(drSOA);
1345
1346 g_recCache->replace(now, zone, QType(QType::SOA), records, {}, {}, true, zone, boost::none, boost::none, vState::Secure);
1347 BOOST_CHECK_EQUAL(g_recCache->size(), 1U);
1348
1349 std::string oldSalt = "ab";
1350 std::string newSalt = "cd";
1351 unsigned int oldIterationsCount = 2;
1352 unsigned int newIterationsCount = 1;
1353 DNSName name("www.powerdns.com");
1354 std::string hashed = hashQNameWithSalt(oldSalt, oldIterationsCount, name);
1355
1356 DNSRecord rec;
1357 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1358 rec.d_type = QType::NSEC3;
1359 rec.d_ttl = now + 10;
1360
1361 NSEC3RecordContent nrc;
1362 nrc.d_algorithm = 1;
1363 nrc.d_flags = 0;
1364 nrc.d_iterations = oldIterationsCount;
1365 nrc.d_salt = oldSalt;
1366 nrc.d_nexthash = hashed;
1367 incrementHash(nrc.d_nexthash);
1368 for (const auto& type : {QType::A}) {
1369 nrc.set(type);
1370 }
1371
1372 rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
1373 auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
1374 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
1375
1376 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1377
1378 int res;
1379 std::vector<DNSRecord> results;
1380
1381 /* we can use the NSEC3s we have */
1382 /* direct match */
1383 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
1384
1385 DNSName other("other.powerdns.com");
1386 /* now we insert a new NSEC3, with a different salt, changing that value for the zone */
1387 hashed = hashQNameWithSalt(newSalt, oldIterationsCount, other);
1388 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1389 rec.d_type = QType::NSEC3;
1390 rec.d_ttl = now + 10;
1391 nrc.d_algorithm = 1;
1392 nrc.d_flags = 0;
1393 nrc.d_iterations = oldIterationsCount;
1394 nrc.d_salt = newSalt;
1395 nrc.d_nexthash = hashed;
1396 incrementHash(nrc.d_nexthash);
1397 for (const auto& type : {QType::A}) {
1398 nrc.set(type);
1399 }
1400
1401 rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
1402 rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
1403 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
1404
1405 /* the existing entries should have been cleared */
1406 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1407
1408 /* we should be able to find a direct match for that name */
1409 /* direct match */
1410 BOOST_CHECK_EQUAL(cache->getDenial(now, other, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
1411
1412 /* but we should not be able to use the other NSEC3s */
1413 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
1414
1415 /* and the same thing but this time updating the iterations count instead of the salt */
1416 DNSName other2("other2.powerdns.com");
1417 hashed = hashQNameWithSalt(newSalt, newIterationsCount, other2);
1418 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1419 rec.d_type = QType::NSEC3;
1420 rec.d_ttl = now + 10;
1421 nrc.d_algorithm = 1;
1422 nrc.d_flags = 0;
1423 nrc.d_iterations = newIterationsCount;
1424 nrc.d_salt = newSalt;
1425 nrc.d_nexthash = hashed;
1426 incrementHash(nrc.d_nexthash);
1427 for (const auto& type : {QType::A}) {
1428 nrc.set(type);
1429 }
1430
1431 rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
1432 rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
1433 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
1434
1435 /* the existing entries should have been cleared */
1436 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1437
1438 /* we should be able to find a direct match for that name */
1439 /* direct match */
1440 BOOST_CHECK_EQUAL(cache->getDenial(now, other2, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
1441
1442 /* but we should not be able to use the other NSEC3s */
1443 BOOST_CHECK_EQUAL(cache->getDenial(now, other, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
1444 }
1445
1446 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor_cases)
1447 {
1448 auto cache = make_unique<AggressiveNSECCache>(10000);
1449 g_recCache = std::make_unique<MemRecursorCache>();
1450
1451 const DNSName zone("powerdns.com");
1452 time_t now = time(nullptr);
1453
1454 /* first we need a SOA */
1455 std::vector<DNSRecord> records;
1456 time_t ttd = now + 30;
1457 DNSRecord drSOA;
1458 drSOA.d_name = zone;
1459 drSOA.d_type = QType::SOA;
1460 drSOA.d_class = QClass::IN;
1461 drSOA.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
1462 drSOA.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation
1463 drSOA.d_place = DNSResourceRecord::ANSWER;
1464 records.push_back(drSOA);
1465
1466 g_recCache->replace(now, zone, QType(QType::SOA), records, {}, {}, true, zone, boost::none, boost::none, vState::Secure);
1467 BOOST_CHECK_EQUAL(g_recCache->size(), 1U);
1468
1469 {
1470 cache = make_unique<AggressiveNSECCache>(10000);
1471 /* insert a NSEC matching the exact name (apex) */
1472 DNSName name("sub.powerdns.com");
1473 DNSRecord rec;
1474 rec.d_name = name;
1475 rec.d_type = QType::NSEC;
1476 rec.d_ttl = now + 10;
1477
1478 NSECRecordContent nrc;
1479 nrc.d_next = DNSName("sub1.powerdns.com");
1480 for (const auto& type : {QType::A}) {
1481 nrc.set(type);
1482 }
1483
1484 rec.setContent(std::make_shared<NSECRecordContent>(nrc));
1485 auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 sub.powerdns.com. data");
1486 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, false);
1487
1488 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1489
1490 /* the cache should now be able to deny other types (except the DS) */
1491 int res;
1492 std::vector<DNSRecord> results;
1493 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
1494 BOOST_CHECK_EQUAL(res, RCode::NoError);
1495 BOOST_CHECK_EQUAL(results.size(), 3U);
1496 /* but not the DS that lives in the parent zone */
1497 results.clear();
1498 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
1499 BOOST_CHECK_EQUAL(results.size(), 0U);
1500 }
1501
1502 {
1503 cache = make_unique<AggressiveNSECCache>(10000);
1504 /* insert a NSEC matching the exact name, but it is an ancestor NSEC (delegation) */
1505 DNSName name("sub.powerdns.com");
1506 DNSRecord rec;
1507 rec.d_name = name;
1508 rec.d_type = QType::NSEC;
1509 rec.d_ttl = now + 10;
1510
1511 NSECRecordContent nrc;
1512 nrc.d_next = DNSName("sub1.powerdns.com");
1513 for (const auto& type : {QType::NS}) {
1514 nrc.set(type);
1515 }
1516
1517 rec.setContent(std::make_shared<NSECRecordContent>(nrc));
1518 auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1519 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, false);
1520
1521 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1522
1523 /* the cache should now be able to deny the DS */
1524 int res;
1525 std::vector<DNSRecord> results;
1526 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
1527 BOOST_CHECK_EQUAL(res, RCode::NoError);
1528 BOOST_CHECK_EQUAL(results.size(), 3U);
1529 /* but not any type that lives in the child zone */
1530 results.clear();
1531 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
1532 }
1533
1534 {
1535 cache = make_unique<AggressiveNSECCache>(10000);
1536 /* insert a NSEC matching the exact name inside a zone (neither apex nor delegation point) */
1537 DNSName name("sub.powerdns.com");
1538 DNSRecord rec;
1539 rec.d_name = name;
1540 rec.d_type = QType::NSEC;
1541 rec.d_ttl = now + 10;
1542
1543 NSECRecordContent nrc;
1544 nrc.d_next = DNSName("sub1.powerdns.com");
1545 for (const auto& type : {QType::A}) {
1546 nrc.set(type);
1547 }
1548
1549 rec.setContent(std::make_shared<NSECRecordContent>(nrc));
1550 auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1551 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, false);
1552
1553 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1554
1555 /* the cache should now be able to deny other types */
1556 int res;
1557 std::vector<DNSRecord> results;
1558 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
1559 BOOST_CHECK_EQUAL(res, RCode::NoError);
1560 BOOST_CHECK_EQUAL(results.size(), 3U);
1561 /* including the DS */
1562 results.clear();
1563 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
1564 BOOST_CHECK_EQUAL(res, RCode::NoError);
1565 BOOST_CHECK_EQUAL(results.size(), 3U);
1566 }
1567
1568 {
1569 /* nxd inside a zone (neither apex nor delegation point) */
1570 cache = make_unique<AggressiveNSECCache>(10000);
1571 /* insert NSEC proving that the name does not exist */
1572 DNSName name("sub.powerdns.com.");
1573 DNSName wc("*.powerdns.com.");
1574
1575 {
1576 DNSRecord rec;
1577 rec.d_name = DNSName("sua.powerdns.com");
1578 rec.d_type = QType::NSEC;
1579 rec.d_ttl = now + 10;
1580
1581 NSECRecordContent nrc;
1582 nrc.d_next = DNSName("suc.powerdns.com");
1583 for (const auto& type : {QType::A, QType::SOA, QType::NS}) {
1584 nrc.set(type);
1585 }
1586
1587 rec.setContent(std::make_shared<NSECRecordContent>(nrc));
1588 auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1589 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, false);
1590
1591 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1592 }
1593 {
1594 /* wildcard */
1595 DNSRecord rec;
1596 rec.d_name = DNSName(").powerdns.com.");
1597 rec.d_type = QType::NSEC;
1598 rec.d_ttl = now + 10;
1599
1600 NSECRecordContent nrc;
1601 nrc.d_next = DNSName("+.powerdns.com.");
1602 for (const auto& type : {QType::NS}) {
1603 nrc.set(type);
1604 }
1605
1606 rec.setContent(std::make_shared<NSECRecordContent>(nrc));
1607 auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1608 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, false);
1609
1610 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U);
1611 }
1612
1613 /* the cache should now be able to deny any type for the name */
1614 int res;
1615 std::vector<DNSRecord> results;
1616 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
1617 BOOST_CHECK_EQUAL(res, RCode::NXDomain);
1618 BOOST_CHECK_EQUAL(results.size(), 5U);
1619
1620 /* including the DS, since we are not at the apex */
1621 results.clear();
1622 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
1623 BOOST_CHECK_EQUAL(res, RCode::NXDomain);
1624 BOOST_CHECK_EQUAL(results.size(), 5U);
1625 }
1626 }
1627
1628 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
1629 {
1630 AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159;
1631 auto cache = make_unique<AggressiveNSECCache>(10000);
1632 g_recCache = std::make_unique<MemRecursorCache>();
1633
1634 const DNSName zone("powerdns.com");
1635 time_t now = time(nullptr);
1636
1637 /* first we need a SOA */
1638 std::vector<DNSRecord> records;
1639 time_t ttd = now + 30;
1640 DNSRecord drSOA;
1641 drSOA.d_name = zone;
1642 drSOA.d_type = QType::SOA;
1643 drSOA.d_class = QClass::IN;
1644 drSOA.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
1645 drSOA.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation
1646 drSOA.d_place = DNSResourceRecord::ANSWER;
1647 records.push_back(drSOA);
1648
1649 g_recCache->replace(now, zone, QType(QType::SOA), records, {}, {}, true, zone, boost::none, boost::none, vState::Secure);
1650 BOOST_CHECK_EQUAL(g_recCache->size(), 1U);
1651
1652 const std::string salt("ab");
1653 const unsigned int iterationsCount = 1;
1654
1655 {
1656 cache = make_unique<AggressiveNSECCache>(10000);
1657 /* insert a NSEC3 matching the exact name (apex) */
1658 DNSName name("sub.powerdns.com");
1659 std::string hashed = hashQNameWithSalt(salt, iterationsCount, name);
1660 DNSRecord rec;
1661 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1662 rec.d_type = QType::NSEC3;
1663 rec.d_ttl = now + 10;
1664
1665 NSEC3RecordContent nrc;
1666 nrc.d_algorithm = 1;
1667 nrc.d_flags = 0;
1668 nrc.d_iterations = iterationsCount;
1669 nrc.d_salt = salt;
1670 nrc.d_nexthash = hashed;
1671 incrementHash(nrc.d_nexthash);
1672 for (const auto& type : {QType::A}) {
1673 nrc.set(type);
1674 }
1675
1676 rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
1677 auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 sub.powerdns.com. data");
1678 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
1679
1680 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1681
1682 /* the cache should now be able to deny other types (except the DS) */
1683 int res;
1684 std::vector<DNSRecord> results;
1685 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
1686 BOOST_CHECK_EQUAL(res, RCode::NoError);
1687 BOOST_CHECK_EQUAL(results.size(), 3U);
1688 /* but not the DS that lives in the parent zone */
1689 results.clear();
1690 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
1691 BOOST_CHECK_EQUAL(results.size(), 0U);
1692 }
1693
1694 {
1695 cache = make_unique<AggressiveNSECCache>(10000);
1696 /* insert a NSEC3 matching the exact name, but it is an ancestor NSEC3 (delegation) */
1697 DNSName name("sub.powerdns.com");
1698 std::string hashed = hashQNameWithSalt(salt, iterationsCount, name);
1699 DNSRecord rec;
1700 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1701 rec.d_type = QType::NSEC3;
1702 rec.d_ttl = now + 10;
1703
1704 NSEC3RecordContent nrc;
1705 nrc.d_algorithm = 1;
1706 nrc.d_flags = 0;
1707 nrc.d_iterations = iterationsCount;
1708 nrc.d_salt = salt;
1709 nrc.d_nexthash = hashed;
1710 incrementHash(nrc.d_nexthash);
1711 for (const auto& type : {QType::NS}) {
1712 nrc.set(type);
1713 }
1714
1715 rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
1716 auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1717 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
1718
1719 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1720
1721 /* the cache should now be able to deny the DS */
1722 int res;
1723 std::vector<DNSRecord> results;
1724 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
1725 BOOST_CHECK_EQUAL(res, RCode::NoError);
1726 BOOST_CHECK_EQUAL(results.size(), 3U);
1727 /* but not any type that lives in the child zone */
1728 results.clear();
1729 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
1730 }
1731
1732 {
1733 cache = make_unique<AggressiveNSECCache>(10000);
1734 /* insert a NSEC3 matching the exact name inside a zone (neither apex nor delegation point) */
1735 DNSName name("sub.powerdns.com");
1736 std::string hashed = hashQNameWithSalt(salt, iterationsCount, name);
1737 DNSRecord rec;
1738 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1739 rec.d_type = QType::NSEC3;
1740 rec.d_ttl = now + 10;
1741
1742 NSEC3RecordContent nrc;
1743 nrc.d_algorithm = 1;
1744 nrc.d_flags = 0;
1745 nrc.d_iterations = iterationsCount;
1746 nrc.d_salt = salt;
1747 nrc.d_nexthash = hashed;
1748 incrementHash(nrc.d_nexthash);
1749 for (const auto& type : {QType::A}) {
1750 nrc.set(type);
1751 }
1752
1753 rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
1754 auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1755 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
1756
1757 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1758
1759 /* the cache should now be able to deny other types */
1760 int res;
1761 std::vector<DNSRecord> results;
1762 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
1763 BOOST_CHECK_EQUAL(res, RCode::NoError);
1764 BOOST_CHECK_EQUAL(results.size(), 3U);
1765 /* including the DS */
1766 results.clear();
1767 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
1768 BOOST_CHECK_EQUAL(res, RCode::NoError);
1769 BOOST_CHECK_EQUAL(results.size(), 3U);
1770 }
1771
1772 {
1773 /* nxd inside a zone (neither apex nor delegation point) */
1774 cache = make_unique<AggressiveNSECCache>(10000);
1775 /* insert NSEC3s proving that the name does not exist */
1776 DNSName name("sub.powerdns.com.");
1777 DNSName closestEncloser("powerdns.com.");
1778 DNSName nextCloser("sub.powerdns.com.");
1779 DNSName wc("*.powerdns.com.");
1780
1781 {
1782 /* closest encloser */
1783 std::string hashed = hashQNameWithSalt(salt, iterationsCount, closestEncloser);
1784 DNSRecord rec;
1785 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1786 rec.d_type = QType::NSEC3;
1787 rec.d_ttl = now + 10;
1788
1789 NSEC3RecordContent nrc;
1790 nrc.d_algorithm = 1;
1791 nrc.d_flags = 0;
1792 nrc.d_iterations = iterationsCount;
1793 nrc.d_salt = salt;
1794 nrc.d_nexthash = hashed;
1795 incrementHash(nrc.d_nexthash);
1796 for (const auto& type : {QType::A, QType::SOA, QType::NS}) {
1797 nrc.set(type);
1798 }
1799
1800 rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
1801 auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1802 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
1803
1804 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1805 }
1806 {
1807 /* next closer */
1808 std::string hashed = hashQNameWithSalt(salt, iterationsCount, nextCloser);
1809 decrementHash(hashed);
1810
1811 DNSRecord rec;
1812 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1813 rec.d_type = QType::NSEC3;
1814 rec.d_ttl = now + 10;
1815
1816 NSEC3RecordContent nrc;
1817 nrc.d_algorithm = 1;
1818 nrc.d_flags = 0;
1819 nrc.d_iterations = iterationsCount;
1820 nrc.d_salt = salt;
1821 nrc.d_nexthash = hashed;
1822 incrementHash(nrc.d_nexthash);
1823 incrementHash(nrc.d_nexthash);
1824 for (const auto& type : {QType::NS}) {
1825 nrc.set(type);
1826 }
1827
1828 rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
1829 auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1830 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
1831
1832 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U);
1833 }
1834 {
1835 /* wildcard */
1836 std::string hashed = hashQNameWithSalt(salt, iterationsCount, wc);
1837 decrementHash(hashed);
1838
1839 DNSRecord rec;
1840 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1841 rec.d_type = QType::NSEC3;
1842 rec.d_ttl = now + 10;
1843
1844 NSEC3RecordContent nrc;
1845 nrc.d_algorithm = 1;
1846 nrc.d_flags = 0;
1847 nrc.d_iterations = iterationsCount;
1848 nrc.d_salt = salt;
1849 nrc.d_nexthash = hashed;
1850 incrementHash(nrc.d_nexthash);
1851 incrementHash(nrc.d_nexthash);
1852 for (const auto& type : {QType::NS}) {
1853 nrc.set(type);
1854 }
1855
1856 rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
1857 auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1858 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
1859
1860 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
1861 }
1862
1863 /* the cache should now be able to deny any type for the name */
1864 int res;
1865 std::vector<DNSRecord> results;
1866 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
1867 BOOST_CHECK_EQUAL(res, RCode::NXDomain);
1868 BOOST_CHECK_EQUAL(results.size(), 7U);
1869
1870 /* including the DS, since we are not at the apex */
1871 results.clear();
1872 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
1873 BOOST_CHECK_EQUAL(res, RCode::NXDomain);
1874 BOOST_CHECK_EQUAL(results.size(), 7U);
1875 }
1876 {
1877 /* we insert NSEC3s coming from the parent zone that could look like a valid denial but are not */
1878 cache = make_unique<AggressiveNSECCache>(10000);
1879
1880 DNSName name("www.sub.powerdns.com.");
1881 DNSName closestEncloser("powerdns.com.");
1882 DNSName nextCloser("sub.powerdns.com.");
1883 DNSName wc("*.powerdns.com.");
1884
1885 {
1886 /* closest encloser */
1887 std::string hashed = hashQNameWithSalt(salt, iterationsCount, closestEncloser);
1888 DNSRecord rec;
1889 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1890 rec.d_type = QType::NSEC3;
1891 rec.d_ttl = now + 10;
1892
1893 NSEC3RecordContent nrc;
1894 nrc.d_algorithm = 1;
1895 nrc.d_flags = 0;
1896 nrc.d_iterations = iterationsCount;
1897 nrc.d_salt = salt;
1898 nrc.d_nexthash = hashed;
1899 incrementHash(nrc.d_nexthash);
1900 /* delegation ! */
1901 for (const auto& type : {QType::NS}) {
1902 nrc.set(type);
1903 }
1904
1905 rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
1906 auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1907 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
1908
1909 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
1910 }
1911 {
1912 /* next closer */
1913 std::string hashed = hashQNameWithSalt(salt, iterationsCount, nextCloser);
1914 decrementHash(hashed);
1915
1916 DNSRecord rec;
1917 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1918 rec.d_type = QType::NSEC3;
1919 rec.d_ttl = now + 10;
1920
1921 NSEC3RecordContent nrc;
1922 nrc.d_algorithm = 1;
1923 nrc.d_flags = 0;
1924 nrc.d_iterations = iterationsCount;
1925 nrc.d_salt = salt;
1926 nrc.d_nexthash = hashed;
1927 incrementHash(nrc.d_nexthash);
1928 incrementHash(nrc.d_nexthash);
1929 for (const auto& type : {QType::A}) {
1930 nrc.set(type);
1931 }
1932
1933 rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
1934 auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1935 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
1936
1937 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U);
1938 }
1939 {
1940 /* wildcard */
1941 std::string hashed = hashQNameWithSalt(salt, iterationsCount, wc);
1942 decrementHash(hashed);
1943
1944 DNSRecord rec;
1945 rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
1946 rec.d_type = QType::NSEC3;
1947 rec.d_ttl = now + 10;
1948
1949 NSEC3RecordContent nrc;
1950 nrc.d_algorithm = 1;
1951 nrc.d_flags = 0;
1952 nrc.d_iterations = iterationsCount;
1953 nrc.d_salt = salt;
1954 nrc.d_nexthash = hashed;
1955 incrementHash(nrc.d_nexthash);
1956 incrementHash(nrc.d_nexthash);
1957 for (const auto& type : {QType::A}) {
1958 nrc.set(type);
1959 }
1960
1961 rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
1962 auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
1963 cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
1964
1965 BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
1966 }
1967
1968 /* the cache should NOT be able to deny the name */
1969 int res;
1970 std::vector<DNSRecord> results;
1971 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
1972 BOOST_CHECK_EQUAL(results.size(), 0U);
1973
1974 /* and the same for the DS */
1975 results.clear();
1976 BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
1977 BOOST_CHECK_EQUAL(results.size(), 0U);
1978 }
1979 }
1980
1981 BOOST_AUTO_TEST_SUITE_END()