1 #define BOOST_TEST_DYN_LINK
2 #define BOOST_TEST_NO_MAIN
7 #include <boost/test/unit_test.hpp>
8 #include <boost/test/floating_point_comparison.hpp>
11 #include "recursor_cache.hh"
13 BOOST_AUTO_TEST_SUITE(recursorcache_cc
)
15 BOOST_AUTO_TEST_CASE(test_RecursorCacheSimple
) {
18 std::vector
<DNSRecord
> records
;
19 std::vector
<std::shared_ptr
<DNSRecord
>> authRecords
;
20 std::vector
<std::shared_ptr
<RRSIGRecordContent
>> signatures
;
21 time_t now
= time(nullptr);
23 BOOST_CHECK_EQUAL(MRC
.size(), 0);
24 MRC
.replace(now
, DNSName("hello"), QType(QType::A
), records
, signatures
, authRecords
, true, boost::none
);
25 BOOST_CHECK_EQUAL(MRC
.size(), 1);
26 BOOST_CHECK_GT(MRC
.bytes(), 1);
27 BOOST_CHECK_EQUAL(MRC
.doWipeCache(DNSName("hello"), false, QType::A
), 1);
28 BOOST_CHECK_EQUAL(MRC
.size(), 0);
29 BOOST_CHECK_EQUAL(MRC
.bytes(), 0);
33 for(counter
= 0; counter
< 100000; ++counter
) {
34 DNSName a
= DNSName("hello ")+DNSName(std::to_string(counter
));
35 BOOST_CHECK_EQUAL(DNSName(a
.toString()), a
);
37 MRC
.replace(now
, a
, QType(QType::A
), records
, signatures
, authRecords
, true, boost::none
);
38 if(!MRC
.doWipeCache(a
, false))
39 BOOST_FAIL("Could not remove entry we just added to the cache!");
40 MRC
.replace(now
, a
, QType(QType::A
), records
, signatures
, authRecords
, true, boost::none
);
43 BOOST_CHECK_EQUAL(MRC
.size(), counter
);
45 uint64_t delcounter
= 0;
46 for(delcounter
=0; delcounter
< counter
/100; ++delcounter
) {
47 DNSName a
= DNSName("hello ")+DNSName(std::to_string(delcounter
));
48 BOOST_CHECK_EQUAL(MRC
.doWipeCache(a
, false, QType::A
), 1);
51 BOOST_CHECK_EQUAL(MRC
.size(), counter
-delcounter
);
53 std::vector
<DNSRecord
> retrieved
;
54 ComboAddress
who("192.0.2.1");
56 int64_t expected
= counter
-delcounter
;
58 for(; delcounter
< counter
; ++delcounter
) {
59 if(MRC
.get(now
, DNSName("hello ")+DNSName(std::to_string(delcounter
)), QType(QType::A
), false, &retrieved
, who
, nullptr)) {
63 BOOST_CHECK_EQUAL(matches
, expected
);
64 BOOST_CHECK_EQUAL(retrieved
.size(), records
.size());
66 MRC
.doWipeCache(DNSName("."), true);
67 BOOST_CHECK_EQUAL(MRC
.size(), 0);
69 time_t ttd
= now
+ 30;
70 DNSName
power("powerdns.com.");
72 ComboAddress
dr1Content("2001:DB8::1");
74 dr1
.d_type
= QType::AAAA
;
75 dr1
.d_class
= QClass::IN
;
76 dr1
.d_content
= std::make_shared
<AAAARecordContent
>(dr1Content
);
77 dr1
.d_ttl
= static_cast<uint32_t>(ttd
);
78 dr1
.d_place
= DNSResourceRecord::ANSWER
;
81 ComboAddress
dr2Content("192.0.2.42");
83 dr2
.d_type
= QType::A
;
84 dr2
.d_class
= QClass::IN
;
85 dr2
.d_content
= std::make_shared
<ARecordContent
>(dr2Content
);
86 dr2
.d_ttl
= static_cast<uint32_t>(ttd
);
87 // the place should not matter to the cache
88 dr2
.d_place
= DNSResourceRecord::AUTHORITY
;
90 // insert a subnet specific entry
91 records
.push_back(dr1
);
92 MRC
.replace(now
, power
, QType(QType::AAAA
), records
, signatures
, authRecords
, true, boost::optional
<Netmask
>("192.0.2.1/25"));
93 BOOST_CHECK_EQUAL(MRC
.size(), 1);
96 // subnet specific should be returned for a matching subnet
97 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::AAAA
), false, &retrieved
, ComboAddress("192.0.2.2"), nullptr), (ttd
-now
));
98 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
99 BOOST_CHECK_EQUAL(getRR
<AAAARecordContent
>(retrieved
.at(0))->getCA().toString(), dr1Content
.toString());
102 // subnet specific should not be returned for a different subnet
103 BOOST_CHECK_LT(MRC
.get(now
, power
, QType(QType::AAAA
), false, &retrieved
, ComboAddress("127.0.0.1"), nullptr), 0);
104 BOOST_CHECK_EQUAL(retrieved
.size(), 0);
107 MRC
.doWipeCache(DNSName("."), true);
108 BOOST_CHECK_EQUAL(MRC
.size(), 0);
110 // insert a NON-subnet specific entry
112 records
.push_back(dr2
);
113 MRC
.replace(now
, power
, QType(QType::A
), records
, signatures
, authRecords
, true, boost::none
);
114 BOOST_CHECK_EQUAL(MRC
.size(), 1);
116 // NON-subnet specific should always be returned
117 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::A
), false, &retrieved
, ComboAddress("127.0.0.1"), nullptr), (ttd
-now
));
118 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
119 BOOST_CHECK_EQUAL(getRR
<ARecordContent
>(retrieved
.at(0))->getCA().toString(), dr2Content
.toString());
122 // insert a subnet specific entry for the same name but a different QType
124 records
.push_back(dr1
);
125 MRC
.replace(now
, power
, QType(QType::AAAA
), records
, signatures
, authRecords
, true, boost::optional
<Netmask
>("192.0.2.1/25"));
126 // we should not have replaced the existing entry
127 BOOST_CHECK_EQUAL(MRC
.size(), 2);
129 // insert a TXT one, we will use that later
131 records
.push_back(dr1
);
132 MRC
.replace(now
, power
, QType(QType::TXT
), records
, signatures
, authRecords
, true, boost::none
);
133 // we should not have replaced any existing entry
134 BOOST_CHECK_EQUAL(MRC
.size(), 3);
136 // we should still get the NON-subnet specific entry
137 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::A
), false, &retrieved
, ComboAddress("127.0.0.1"), nullptr), (ttd
-now
));
138 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
139 BOOST_CHECK_EQUAL(getRR
<ARecordContent
>(retrieved
.at(0))->getCA().toString(), dr2Content
.toString());
142 // we should get the subnet specific entry if we are from the right subnet
143 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::AAAA
), false, &retrieved
, ComboAddress("192.0.2.3"), nullptr), (ttd
-now
));
144 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
145 BOOST_CHECK_EQUAL(getRR
<AAAARecordContent
>(retrieved
.at(0))->getCA().toString(), dr1Content
.toString());
148 // but nothing from a different subnet
149 BOOST_CHECK_LT(MRC
.get(now
, power
, QType(QType::AAAA
), false, &retrieved
, ComboAddress("127.0.0.1"), nullptr), 0);
150 BOOST_CHECK_EQUAL(retrieved
.size(), 0);
153 // QType::ANY should return any qtype, so from the right subnet we should get all of them
154 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::ANY
), false, &retrieved
, ComboAddress("192.0.2.3"), nullptr), (ttd
-now
));
155 BOOST_CHECK_EQUAL(retrieved
.size(), 3);
156 for (const auto& rec
: retrieved
) {
157 BOOST_CHECK(rec
.d_type
== QType::A
|| rec
.d_type
== QType::AAAA
|| rec
.d_type
== QType::TXT
);
159 // check that the place is always set to ANSWER
160 for (const auto& rec
: retrieved
) {
161 BOOST_CHECK(rec
.d_place
== DNSResourceRecord::ANSWER
);
165 // but only the non-subnet specific from the another subnet
166 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::ANY
), false, &retrieved
, ComboAddress("127.0.0.1"), nullptr), (ttd
-now
));
167 BOOST_CHECK_EQUAL(retrieved
.size(), 2);
168 for (const auto& rec
: retrieved
) {
169 BOOST_CHECK(rec
.d_type
== QType::A
|| rec
.d_type
== QType::TXT
);
173 // QType::ADDR should return both A and AAAA but no TXT, so two entries from the right subnet
174 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::ADDR
), false, &retrieved
, ComboAddress("192.0.2.3"), nullptr), (ttd
-now
));
175 BOOST_CHECK_EQUAL(retrieved
.size(), 2);
176 for (const auto& rec
: retrieved
) {
177 BOOST_CHECK(rec
.d_type
== QType::A
|| rec
.d_type
== QType::AAAA
);
181 // but only the non-subnet specific one from the another subnet
182 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::ADDR
), false, &retrieved
, ComboAddress("127.0.0.1"), nullptr), (ttd
-now
));
183 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
184 BOOST_CHECK(retrieved
.at(0).d_type
== QType::A
);
187 // entries are only valid until ttd, we should not get anything after that because they are expired
188 BOOST_CHECK_LT(MRC
.get(ttd
+ 5, power
, QType(QType::ADDR
), false, &retrieved
, ComboAddress("127.0.0.1"), nullptr), 0);
189 BOOST_CHECK_EQUAL(retrieved
.size(), 0);
192 // let's age the records for our existing QType::TXT entry so they are now only valid for 5s
194 BOOST_CHECK_EQUAL(MRC
.doAgeCache(now
, power
, QType::TXT
, newTTL
), true);
196 // we should still be able to retrieve it
197 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::TXT
), false, &retrieved
, ComboAddress("127.0.0.1"), nullptr), newTTL
);
198 BOOST_CHECK_EQUAL(retrieved
.size(), 1);
199 BOOST_CHECK(retrieved
.at(0).d_type
== QType::TXT
);
200 // please note that this is still a TTD at this point
201 BOOST_CHECK_EQUAL(retrieved
.at(0).d_ttl
, now
+ newTTL
);
204 // but 10s later it should be gone
205 BOOST_CHECK_LT(MRC
.get(now
+ 10, power
, QType(QType::TXT
), false, &retrieved
, ComboAddress("127.0.0.1"), nullptr), 0);
206 BOOST_CHECK_EQUAL(retrieved
.size(), 0);
210 MRC
.doWipeCache(DNSName("."), true);
211 BOOST_CHECK_EQUAL(MRC
.size(), 0);
214 // insert auth record
215 records
.push_back(dr2
);
216 MRC
.replace(now
, power
, QType(QType::A
), records
, signatures
, authRecords
, true, boost::none
);
217 BOOST_CHECK_EQUAL(MRC
.size(), 1);
218 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::A
), false, &retrieved
, ComboAddress("127.0.0.1"), nullptr), (ttd
-now
));
219 BOOST_CHECK_EQUAL(retrieved
.size(), 1);
222 ComboAddress
dr3Content("192.0.2.84");
224 dr3
.d_type
= QType::A
;
225 dr3
.d_class
= QClass::IN
;
226 dr3
.d_content
= std::make_shared
<ARecordContent
>(dr3Content
);
227 dr3
.d_ttl
= static_cast<uint32_t>(ttd
+ 100);
228 // the place should not matter to the cache
229 dr3
.d_place
= DNSResourceRecord::AUTHORITY
;
231 // this is important for our tests
232 BOOST_REQUIRE_GT(dr3
.d_ttl
, ttd
);
235 records
.push_back(dr3
);
237 // non-auth should not replace valid auth
238 MRC
.replace(now
, power
, QType(QType::A
), records
, signatures
, authRecords
, false, boost::none
);
239 BOOST_CHECK_EQUAL(MRC
.size(), 1);
240 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::A
), false, &retrieved
, ComboAddress("127.0.0.1"), nullptr), (ttd
-now
));
241 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
242 BOOST_CHECK_EQUAL(getRR
<ARecordContent
>(retrieved
.at(0))->getCA().toString(), dr2Content
.toString());
244 // but non-auth _should_ replace expired auth
245 MRC
.replace(ttd
+ 1, power
, QType(QType::A
), records
, signatures
, authRecords
, false, boost::none
);
246 BOOST_CHECK_EQUAL(MRC
.size(), 1);
247 BOOST_CHECK_EQUAL(MRC
.get(ttd
+ 1, power
, QType(QType::A
), false, &retrieved
, ComboAddress("127.0.0.1"), nullptr), (dr3
.d_ttl
- (ttd
+ 1)));
248 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
249 BOOST_CHECK_EQUAL(getRR
<ARecordContent
>(retrieved
.at(0))->getCA().toString(), dr3Content
.toString());
251 // auth should replace non-auth
253 records
.push_back(dr2
);
254 MRC
.replace(now
, power
, QType(QType::A
), records
, signatures
, authRecords
, false, boost::none
);
255 BOOST_CHECK_EQUAL(MRC
.size(), 1);
256 // let's first check that non-auth is not returned when we need authoritative data
257 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::A
), true, &retrieved
, ComboAddress("127.0.0.1"), nullptr), -now
);
258 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::A
), false, &retrieved
, ComboAddress("127.0.0.1"), nullptr), (ttd
-now
));
259 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
260 BOOST_CHECK_EQUAL(getRR
<ARecordContent
>(retrieved
.at(0))->getCA().toString(), dr2Content
.toString());
262 /**** Most specific netmask tests ****/
265 MRC
.doWipeCache(DNSName("."), true);
266 BOOST_CHECK_EQUAL(MRC
.size(), 0);
269 // insert an entry for 192.0.0.1/8
271 records
.push_back(dr2
);
272 MRC
.replace(now
, power
, QType(QType::A
), records
, signatures
, authRecords
, true, boost::optional
<Netmask
>("192.0.0.1/8"));
273 BOOST_CHECK_EQUAL(MRC
.size(), 1);
275 /* same as dr2 except for the actual IP */
277 ComboAddress
dr4Content("192.0.2.126");
279 dr4
.d_type
= QType::A
;
280 dr4
.d_class
= QClass::IN
;
281 dr4
.d_content
= std::make_shared
<ARecordContent
>(dr4Content
);
282 dr4
.d_ttl
= static_cast<uint32_t>(ttd
);
283 dr4
.d_place
= DNSResourceRecord::AUTHORITY
;
285 // insert an other entry but for 192.168.0.1/31
287 records
.push_back(dr4
);
288 MRC
.replace(now
, power
, QType(QType::A
), records
, signatures
, authRecords
, true, boost::optional
<Netmask
>("192.168.0.1/31"));
289 // we should not have replaced any existing entry
290 BOOST_CHECK_EQUAL(MRC
.size(), 2);
292 // insert the same than the first one but for 192.168.0.2/32
294 records
.push_back(dr2
);
295 MRC
.replace(now
, power
, QType(QType::A
), records
, signatures
, authRecords
, true, boost::optional
<Netmask
>("192.168.0.2/32"));
296 // we should not have replaced any existing entry
297 BOOST_CHECK_EQUAL(MRC
.size(), 3);
299 // we should get the most specific entry for 192.168.0.1, so the second one
300 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::A
), false, &retrieved
, ComboAddress("192.168.0.1"), nullptr), (ttd
-now
));
301 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
302 BOOST_CHECK_EQUAL(getRR
<ARecordContent
>(retrieved
.at(0))->getCA().toString(), dr4Content
.toString());
306 MRC
.doWipeCache(DNSName("."), true);
307 BOOST_CHECK_EQUAL(MRC
.size(), 0);
310 // insert an entry for 192.0.0.1/8, non auth
312 records
.push_back(dr2
);
313 MRC
.replace(now
, power
, QType(QType::A
), records
, signatures
, authRecords
, false, boost::optional
<Netmask
>("192.0.0.1/8"));
314 BOOST_CHECK_EQUAL(MRC
.size(), 1);
316 // we should not get it when we need authoritative data
317 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::A
), true, &retrieved
, ComboAddress("192.168.0.1"), nullptr), -1);
318 BOOST_REQUIRE_EQUAL(retrieved
.size(), 0);
321 // but we should when we are OK with non-auth
322 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::A
), false, &retrieved
, ComboAddress("192.168.0.1"), nullptr), (ttd
-now
));
323 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
324 BOOST_CHECK_EQUAL(getRR
<ARecordContent
>(retrieved
.at(0))->getCA().toString(), dr2Content
.toString());
327 catch(const PDNSException
& e
) {
328 cerr
<<"Had error: "<<e
.reason
<<endl
;
333 BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingExpiredEntries
) {
334 MemRecursorCache MRC
;
336 std::vector
<DNSRecord
> records
;
337 std::vector
<std::shared_ptr
<RRSIGRecordContent
>> signatures
;
338 std::vector
<std::shared_ptr
<DNSRecord
>> authRecs
;
339 BOOST_CHECK_EQUAL(MRC
.size(), 0);
340 time_t now
= time(nullptr);
341 DNSName
power1("powerdns.com.");
342 DNSName
power2("powerdns-1.com.");
343 time_t ttd
= now
- 30;
344 std::vector
<DNSRecord
> retrieved
;
345 ComboAddress
who("192.0.2.1");
347 /* entry for power, which expired 30s ago */
349 ComboAddress
dr1Content("2001:DB8::1");
351 dr1
.d_type
= QType::AAAA
;
352 dr1
.d_class
= QClass::IN
;
353 dr1
.d_content
= std::make_shared
<AAAARecordContent
>(dr1Content
);
354 dr1
.d_ttl
= static_cast<uint32_t>(ttd
);
355 dr1
.d_place
= DNSResourceRecord::ANSWER
;
357 /* entry for power1, which expired 30 ago too */
359 ComboAddress
dr2Content("2001:DB8::2");
361 dr2
.d_type
= QType::AAAA
;
362 dr2
.d_class
= QClass::IN
;
363 dr2
.d_content
= std::make_shared
<AAAARecordContent
>(dr2Content
);
364 dr2
.d_ttl
= static_cast<uint32_t>(ttd
);
365 dr2
.d_place
= DNSResourceRecord::ANSWER
;
367 /* insert both entries */
368 records
.push_back(dr1
);
369 MRC
.replace(now
, power1
, QType(dr1
.d_type
), records
, signatures
, authRecs
, true, boost::none
);
371 records
.push_back(dr2
);
372 MRC
.replace(now
, power2
, QType(dr2
.d_type
), records
, signatures
, authRecs
, true, boost::none
);
374 BOOST_CHECK_EQUAL(MRC
.size(), 2);
376 /* the one for power2 having been inserted
377 more recently should be removed last */
378 /* we ask that only entry remains in the cache */
380 BOOST_CHECK_EQUAL(MRC
.size(), 1);
382 /* the remaining entry should be power2, but to get it
383 we need to go back in the past a bit */
384 BOOST_CHECK_EQUAL(MRC
.get(ttd
- 1, power2
, QType(dr2
.d_type
), false, &retrieved
, who
, nullptr), 1);
385 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
386 BOOST_CHECK_EQUAL(getRR
<AAAARecordContent
>(retrieved
.at(0))->getCA().toString(), dr2Content
.toString());
387 /* check that power1 is gone */
388 BOOST_CHECK_EQUAL(MRC
.get(ttd
- 1, power1
, QType(dr1
.d_type
), false, &retrieved
, who
, nullptr), -1);
390 /* clear everything up */
391 MRC
.doWipeCache(DNSName("."), true);
392 BOOST_CHECK_EQUAL(MRC
.size(), 0);
395 /* insert both entries back */
396 records
.push_back(dr1
);
397 MRC
.replace(now
, power1
, QType(dr1
.d_type
), records
, signatures
, authRecs
, true, boost::none
);
399 records
.push_back(dr2
);
400 MRC
.replace(now
, power2
, QType(dr2
.d_type
), records
, signatures
, authRecs
, true, boost::none
);
402 BOOST_CHECK_EQUAL(MRC
.size(), 2);
404 /* trigger a miss (expired) for power2 */
405 BOOST_CHECK_EQUAL(MRC
.get(now
, power2
, QType(dr2
.d_type
), false, &retrieved
, who
, nullptr), -now
);
407 /* power2 should have been moved to the front of the expunge
408 queue, and should this time be removed first */
409 /* we ask that only entry remains in the cache */
411 BOOST_CHECK_EQUAL(MRC
.size(), 1);
413 /* the remaining entry should be power1, but to get it
414 we need to go back in the past a bit */
415 BOOST_CHECK_EQUAL(MRC
.get(ttd
- 1, power1
, QType(dr1
.d_type
), false, &retrieved
, who
, nullptr), 1);
416 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
417 BOOST_CHECK_EQUAL(getRR
<AAAARecordContent
>(retrieved
.at(0))->getCA().toString(), dr1Content
.toString());
418 /* check that power2 is gone */
419 BOOST_CHECK_EQUAL(MRC
.get(ttd
- 1, power2
, QType(dr2
.d_type
), false, &retrieved
, who
, nullptr), -1);
422 BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries
) {
423 MemRecursorCache MRC
;
425 std::vector
<DNSRecord
> records
;
426 std::vector
<std::shared_ptr
<RRSIGRecordContent
>> signatures
;
427 std::vector
<std::shared_ptr
<DNSRecord
>> authRecs
;
428 BOOST_CHECK_EQUAL(MRC
.size(), 0);
429 time_t now
= time(nullptr);
430 DNSName
power1("powerdns.com.");
431 DNSName
power2("powerdns-1.com.");
432 time_t ttd
= now
+ 30;
433 std::vector
<DNSRecord
> retrieved
;
434 ComboAddress
who("192.0.2.1");
436 /* entry for power, which will expire in 30s */
438 ComboAddress
dr1Content("2001:DB8::1");
440 dr1
.d_type
= QType::AAAA
;
441 dr1
.d_class
= QClass::IN
;
442 dr1
.d_content
= std::make_shared
<AAAARecordContent
>(dr1Content
);
443 dr1
.d_ttl
= static_cast<uint32_t>(ttd
);
444 dr1
.d_place
= DNSResourceRecord::ANSWER
;
446 /* entry for power1, which will expire in 30s too */
448 ComboAddress
dr2Content("2001:DB8::2");
450 dr2
.d_type
= QType::AAAA
;
451 dr2
.d_class
= QClass::IN
;
452 dr2
.d_content
= std::make_shared
<AAAARecordContent
>(dr2Content
);
453 dr2
.d_ttl
= static_cast<uint32_t>(ttd
);
454 dr2
.d_place
= DNSResourceRecord::ANSWER
;
456 /* insert both entries */
457 records
.push_back(dr1
);
458 MRC
.replace(now
, power1
, QType(dr1
.d_type
), records
, signatures
, authRecs
, true, boost::none
);
460 records
.push_back(dr2
);
461 MRC
.replace(now
, power2
, QType(dr2
.d_type
), records
, signatures
, authRecs
, true, boost::none
);
463 BOOST_CHECK_EQUAL(MRC
.size(), 2);
465 /* the one for power2 having been inserted
466 more recently should be removed last */
467 /* we ask that only entry remains in the cache */
469 BOOST_CHECK_EQUAL(MRC
.size(), 1);
471 /* the remaining entry should be power2 */
472 BOOST_CHECK_EQUAL(MRC
.get(now
, power2
, QType(dr2
.d_type
), false, &retrieved
, who
, nullptr), ttd
-now
);
473 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
474 BOOST_CHECK_EQUAL(getRR
<AAAARecordContent
>(retrieved
.at(0))->getCA().toString(), dr2Content
.toString());
475 /* check that power1 is gone */
476 BOOST_CHECK_EQUAL(MRC
.get(now
, power1
, QType(dr1
.d_type
), false, &retrieved
, who
, nullptr), -1);
478 /* clear everything up */
479 MRC
.doWipeCache(DNSName("."), true);
480 BOOST_CHECK_EQUAL(MRC
.size(), 0);
483 /* insert both entries back */
484 records
.push_back(dr1
);
485 MRC
.replace(now
, power1
, QType(dr1
.d_type
), records
, signatures
, authRecs
, true, boost::none
);
487 records
.push_back(dr2
);
488 MRC
.replace(now
, power2
, QType(dr2
.d_type
), records
, signatures
, authRecs
, true, boost::none
);
490 BOOST_CHECK_EQUAL(MRC
.size(), 2);
492 /* replace the entry for power1 */
493 records
.push_back(dr1
);
494 MRC
.replace(now
, power1
, QType(dr1
.d_type
), records
, signatures
, authRecs
, true, boost::none
);
496 BOOST_CHECK_EQUAL(MRC
.size(), 2);
498 /* the replaced entry for power1 should have been moved
499 to the back of the expunge queue, so power2 should be at the front
500 and should this time be removed first */
501 /* we ask that only entry remains in the cache */
503 BOOST_CHECK_EQUAL(MRC
.size(), 1);
505 /* the remaining entry should be power1 */
506 BOOST_CHECK_EQUAL(MRC
.get(now
, power1
, QType(dr1
.d_type
), false, &retrieved
, who
, nullptr), ttd
-now
);
507 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
508 BOOST_CHECK_EQUAL(getRR
<AAAARecordContent
>(retrieved
.at(0))->getCA().toString(), dr1Content
.toString());
509 /* check that power2 is gone */
510 BOOST_CHECK_EQUAL(MRC
.get(now
, power2
, QType(dr2
.d_type
), false, &retrieved
, who
, nullptr), -1);
512 /* clear everything up */
513 MRC
.doWipeCache(DNSName("."), true);
514 BOOST_CHECK_EQUAL(MRC
.size(), 0);
517 /* insert both entries back */
518 records
.push_back(dr1
);
519 MRC
.replace(now
, power1
, QType(dr1
.d_type
), records
, signatures
, authRecs
, true, boost::none
);
521 records
.push_back(dr2
);
522 MRC
.replace(now
, power2
, QType(dr2
.d_type
), records
, signatures
, authRecs
, true, boost::none
);
524 BOOST_CHECK_EQUAL(MRC
.size(), 2);
526 /* get a hit for power1 */
527 BOOST_CHECK_EQUAL(MRC
.get(now
, power1
, QType(dr1
.d_type
), false, &retrieved
, who
, nullptr), ttd
-now
);
528 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
529 BOOST_CHECK_EQUAL(getRR
<AAAARecordContent
>(retrieved
.at(0))->getCA().toString(), dr1Content
.toString());
531 /* the entry for power1 should have been moved to the back of the expunge queue
532 due to the hit, so power2 should be at the front and should this time be removed first */
533 /* we ask that only entry remains in the cache */
535 BOOST_CHECK_EQUAL(MRC
.size(), 1);
537 /* the remaining entry should be power1 */
538 BOOST_CHECK_EQUAL(MRC
.get(now
, power1
, QType(dr1
.d_type
), false, &retrieved
, who
, nullptr), ttd
-now
);
539 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
540 BOOST_CHECK_EQUAL(getRR
<AAAARecordContent
>(retrieved
.at(0))->getCA().toString(), dr1Content
.toString());
541 /* check that power2 is gone */
542 BOOST_CHECK_EQUAL(MRC
.get(now
, power2
, QType(dr2
.d_type
), false, &retrieved
, who
, nullptr), -1);
545 BOOST_CHECK_EQUAL(MRC
.size(), 0);
547 /* add a lot of netmask-specific entries */
548 for (size_t i
= 0; i
<= 255; i
++) {
552 ComboAddress
r1Content("192.0.2." + std::to_string(i
));
554 r1
.d_type
= QType::A
;
555 r1
.d_class
= QClass::IN
;
556 r1
.d_content
= std::make_shared
<ARecordContent
>(r1Content
);
557 r1
.d_ttl
= static_cast<uint32_t>(ttd
);
558 r1
.d_place
= DNSResourceRecord::ANSWER
;
559 records
.push_back(r1
);
561 MRC
.replace(now
, power1
, QType(QType::A
), records
, signatures
, authRecs
, true, Netmask(r1Content
, 32));
564 BOOST_CHECK_EQUAL(MRC
.size(), 256);
565 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 1);
567 /* remove a bit less than half of them */
570 BOOST_CHECK_EQUAL(MRC
.size(), keep
);
571 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 1);
573 /* check that we can still retrieve the remaining ones */
575 for (size_t i
= 0; i
<= 255; i
++) {
577 ComboAddress
whoLoop("192.0.2." + std::to_string(i
));
579 auto ret
= MRC
.get(now
, power1
, QType(QType::A
), false, &retrieved
, whoLoop
);
581 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
582 BOOST_CHECK_EQUAL(getRR
<ARecordContent
>(retrieved
.at(0))->getCA().toString(), whoLoop
.toString());
586 BOOST_REQUIRE_EQUAL(ret
, -1);
587 BOOST_REQUIRE_EQUAL(retrieved
.size(), 0);
591 BOOST_CHECK_EQUAL(found
, keep
);
593 /* remove the rest */
595 BOOST_CHECK_EQUAL(MRC
.size(), 0);
596 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 0);
599 BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex
) {
600 MemRecursorCache MRC
;
602 const DNSName
power("powerdns.com.");
603 std::vector
<DNSRecord
> records
;
604 std::vector
<std::shared_ptr
<DNSRecord
>> authRecords
;
605 std::vector
<std::shared_ptr
<RRSIGRecordContent
>> signatures
;
606 time_t now
= time(nullptr);
607 std::vector
<DNSRecord
> retrieved
;
608 ComboAddress
who("192.0.2.1");
611 time_t ttd
= now
+ ttl
;
613 ComboAddress
dr1Content("192.0.2.255");
615 dr1
.d_type
= QType::A
;
616 dr1
.d_class
= QClass::IN
;
617 dr1
.d_content
= std::make_shared
<ARecordContent
>(dr1Content
);
618 dr1
.d_ttl
= static_cast<uint32_t>(ttd
);
619 dr1
.d_place
= DNSResourceRecord::ANSWER
;
622 ComboAddress
dr2Content("192.0.2.127");
624 dr2
.d_type
= QType::A
;
625 dr2
.d_class
= QClass::IN
;
626 dr2
.d_content
= std::make_shared
<ARecordContent
>(dr2Content
);
627 dr2
.d_ttl
= static_cast<uint32_t>(now
+ 5);
628 dr2
.d_place
= DNSResourceRecord::ANSWER
;
630 BOOST_CHECK_EQUAL(MRC
.size(), 0);
631 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 0);
633 /* no entry in the ECS index, no non-specific entry either */
635 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::A
), false, &retrieved
, who
), -1);
637 /* insert a non-specific entry */
638 records
.push_back(dr1
);
639 MRC
.replace(now
, power
, QType(QType::A
), records
, signatures
, authRecords
, true, boost::none
);
641 BOOST_CHECK_EQUAL(MRC
.size(), 1);
642 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 0);
644 /* retrieve the non-specific entry */
646 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::A
), false, &retrieved
, who
), ttd
- now
);
647 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
648 BOOST_CHECK_EQUAL(getRR
<ARecordContent
>(retrieved
.at(0))->getCA().toString(), dr1Content
.toString());
650 /* wipe everything */
652 BOOST_CHECK_EQUAL(MRC
.size(), 0);
653 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 0);
655 /* insert a specific entry */
656 MRC
.replace(now
, power
, QType(QType::A
), records
, signatures
, authRecords
, true, Netmask("192.0.2.0/31"));
658 BOOST_CHECK_EQUAL(MRC
.size(), 1);
659 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 1);
661 /* there is an ECS index for that entry but no match, and no non-specific entry */
663 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::A
), false, &retrieved
, ComboAddress("192.0.2.4")), -1);
664 BOOST_REQUIRE_EQUAL(retrieved
.size(), 0);
666 /* there is an ECS index for that entry and we get a match */
668 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::A
), false, &retrieved
, ComboAddress("192.0.2.1")), ttd
- now
);
669 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
670 BOOST_CHECK_EQUAL(getRR
<ARecordContent
>(retrieved
.at(0))->getCA().toString(), dr1Content
.toString());
672 /* there is an ECS index for that entry and we get a match,
673 but it has expired. No other match, no non-specific entry */
675 BOOST_CHECK_EQUAL(MRC
.get(now
+ ttl
+ 1, power
, QType(QType::A
), false, &retrieved
, ComboAddress("192.0.2.1")), -1);
676 BOOST_REQUIRE_EQUAL(retrieved
.size(), 0);
678 /* The ECS index should now be empty, but the cache entry has not been expunged yet */
679 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 0);
680 BOOST_CHECK_EQUAL(MRC
.size(), 1);
682 /* wipe everything */
684 BOOST_CHECK_EQUAL(MRC
.size(), 0);
685 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 0);
687 /* insert a specific entry */
688 MRC
.replace(now
, power
, QType(QType::A
), records
, signatures
, authRecords
, true, Netmask("192.0.2.0/24"));
690 BOOST_CHECK_EQUAL(MRC
.size(), 1);
691 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 1);
693 /* insert a slightly more specific one, but expiring sooner */
695 records
.push_back(dr2
);
696 MRC
.replace(now
, power
, QType(QType::A
), records
, signatures
, authRecords
, true, Netmask("192.0.2.0/26"));
698 BOOST_CHECK_EQUAL(MRC
.size(), 2);
699 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 1);
701 /* check that we get the most specific one as long as it's still valid */
703 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::A
), false, &retrieved
, ComboAddress("192.0.2.1")), 5);
704 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
705 BOOST_CHECK_EQUAL(getRR
<ARecordContent
>(retrieved
.at(0))->getCA().toString(), dr2Content
.toString());
707 /* there is an ECS index for that entry and we get a match,
709 The second ECS is a match too, and is valid. */
711 BOOST_CHECK_EQUAL(MRC
.get(now
+ 5 + 1, power
, QType(QType::A
), false, &retrieved
, ComboAddress("192.0.2.1")), (ttd
- (now
+5 + 1)));
712 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
713 BOOST_CHECK_EQUAL(getRR
<ARecordContent
>(retrieved
.at(0))->getCA().toString(), dr1Content
.toString());
715 /* The ECS index should not be empty */
716 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 1);
717 BOOST_CHECK_EQUAL(MRC
.size(), 2);
719 /* wipe everything */
721 BOOST_CHECK_EQUAL(MRC
.size(), 0);
722 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 0);
724 /* insert a non-specific entry */
726 records
.push_back(dr1
);
727 MRC
.replace(now
, power
, QType(QType::A
), records
, signatures
, authRecords
, true, boost::none
);
729 BOOST_CHECK_EQUAL(MRC
.size(), 1);
730 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 0);
732 /* insert a subnet-specific entry */
734 records
.push_back(dr2
);
735 MRC
.replace(now
, power
, QType(QType::A
), records
, signatures
, authRecords
, true, Netmask("192.0.2.42/32"));
737 BOOST_CHECK_EQUAL(MRC
.size(), 2);
738 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 1);
740 /* there is an ECS index for that entry and it doesn't match. No other match, but we have a non-specific entry */
742 BOOST_CHECK_EQUAL(MRC
.get(now
, power
, QType(QType::A
), false, &retrieved
, ComboAddress("192.0.2.255")), ttd
- now
);
743 BOOST_REQUIRE_EQUAL(retrieved
.size(), 1);
744 BOOST_CHECK_EQUAL(getRR
<ARecordContent
>(retrieved
.at(0))->getCA().toString(), dr1Content
.toString());
746 BOOST_CHECK_EQUAL(MRC
.size(), 2);
747 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 1);
749 /* wipe everything */
751 BOOST_CHECK_EQUAL(MRC
.size(), 0);
752 BOOST_CHECK_EQUAL(MRC
.ecsIndexSize(), 0);
755 BOOST_AUTO_TEST_SUITE_END()