]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/recursordist/test-recursorcache_cc.cc
rec: Don't shadow variables
[thirdparty/pdns.git] / pdns / recursordist / test-recursorcache_cc.cc
1 #define BOOST_TEST_DYN_LINK
2 #define BOOST_TEST_NO_MAIN
3
4 #ifdef HAVE_CONFIG_H
5 #include "config.h"
6 #endif
7 #include <boost/test/unit_test.hpp>
8 #include <boost/test/floating_point_comparison.hpp>
9
10 #include "iputils.hh"
11 #include "recursor_cache.hh"
12
13 BOOST_AUTO_TEST_SUITE(recursorcache_cc)
14
15 BOOST_AUTO_TEST_CASE(test_RecursorCacheSimple) {
16 MemRecursorCache MRC;
17
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);
22
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);
30
31 uint64_t counter = 0;
32 try {
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);
36
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);
41 }
42
43 BOOST_CHECK_EQUAL(MRC.size(), counter);
44
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);
49 }
50
51 BOOST_CHECK_EQUAL(MRC.size(), counter-delcounter);
52
53 std::vector<DNSRecord> retrieved;
54 ComboAddress who("192.0.2.1");
55 uint64_t matches = 0;
56 int64_t expected = counter-delcounter;
57
58 for(; delcounter < counter; ++delcounter) {
59 if(MRC.get(now, DNSName("hello ")+DNSName(std::to_string(delcounter)), QType(QType::A), false, &retrieved, who, nullptr)) {
60 matches++;
61 }
62 }
63 BOOST_CHECK_EQUAL(matches, expected);
64 BOOST_CHECK_EQUAL(retrieved.size(), records.size());
65
66 MRC.doWipeCache(DNSName("."), true);
67 BOOST_CHECK_EQUAL(MRC.size(), 0);
68
69 time_t ttd = now + 30;
70 DNSName power("powerdns.com.");
71 DNSRecord dr1;
72 ComboAddress dr1Content("2001:DB8::1");
73 dr1.d_name = power;
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;
79
80 DNSRecord dr2;
81 ComboAddress dr2Content("192.0.2.42");
82 dr2.d_name = power;
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;
89
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);
94
95 retrieved.clear();
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());
100
101 retrieved.clear();
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);
105
106 // remove everything
107 MRC.doWipeCache(DNSName("."), true);
108 BOOST_CHECK_EQUAL(MRC.size(), 0);
109
110 // insert a NON-subnet specific entry
111 records.clear();
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);
115
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());
120 retrieved.clear();
121
122 // insert a subnet specific entry for the same name but a different QType
123 records.clear();
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);
128
129 // insert a TXT one, we will use that later
130 records.clear();
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);
135
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());
140 retrieved.clear();
141
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());
146 retrieved.clear();
147
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);
151 retrieved.clear();
152
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);
158 }
159 // check that the place is always set to ANSWER
160 for (const auto& rec : retrieved) {
161 BOOST_CHECK(rec.d_place == DNSResourceRecord::ANSWER);
162 }
163 retrieved.clear();
164
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);
170 }
171 retrieved.clear();
172
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);
178 }
179 retrieved.clear();
180
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);
185 retrieved.clear();
186
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);
190 retrieved.clear();
191
192 // let's age the records for our existing QType::TXT entry so they are now only valid for 5s
193 uint32_t newTTL = 5;
194 BOOST_CHECK_EQUAL(MRC.doAgeCache(now, power, QType::TXT, newTTL), true);
195
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);
202 retrieved.clear();
203
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);
207 retrieved.clear();
208
209 // wipe everything
210 MRC.doWipeCache(DNSName("."), true);
211 BOOST_CHECK_EQUAL(MRC.size(), 0);
212 records.clear();
213
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);
220
221 DNSRecord dr3;
222 ComboAddress dr3Content("192.0.2.84");
223 dr3.d_name = power;
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;
230
231 // this is important for our tests
232 BOOST_REQUIRE_GT(dr3.d_ttl, ttd);
233
234 records.clear();
235 records.push_back(dr3);
236
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());
243
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());
250
251 // auth should replace non-auth
252 records.clear();
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());
261
262 /**** Most specific netmask tests ****/
263
264 // wipe everything
265 MRC.doWipeCache(DNSName("."), true);
266 BOOST_CHECK_EQUAL(MRC.size(), 0);
267 records.clear();
268
269 // insert an entry for 192.0.0.1/8
270 records.clear();
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);
274
275 /* same as dr2 except for the actual IP */
276 DNSRecord dr4;
277 ComboAddress dr4Content("192.0.2.126");
278 dr4.d_name = power;
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;
284
285 // insert an other entry but for 192.168.0.1/31
286 records.clear();
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);
291
292 // insert the same than the first one but for 192.168.0.2/32
293 records.clear();
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);
298
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());
303 retrieved.clear();
304
305 // wipe everything
306 MRC.doWipeCache(DNSName("."), true);
307 BOOST_CHECK_EQUAL(MRC.size(), 0);
308 records.clear();
309
310 // insert an entry for 192.0.0.1/8, non auth
311 records.clear();
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);
315
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);
319 retrieved.clear();
320
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());
325 retrieved.clear();
326 }
327 catch(const PDNSException& e) {
328 cerr<<"Had error: "<<e.reason<<endl;
329 throw;
330 }
331 }
332
333 BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingExpiredEntries) {
334 MemRecursorCache MRC;
335
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");
346
347 /* entry for power, which expired 30s ago */
348 DNSRecord dr1;
349 ComboAddress dr1Content("2001:DB8::1");
350 dr1.d_name = power1;
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;
356
357 /* entry for power1, which expired 30 ago too */
358 DNSRecord dr2;
359 ComboAddress dr2Content("2001:DB8::2");
360 dr2.d_name = power2;
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;
366
367 /* insert both entries */
368 records.push_back(dr1);
369 MRC.replace(now, power1, QType(dr1.d_type), records, signatures, authRecs, true, boost::none);
370 records.clear();
371 records.push_back(dr2);
372 MRC.replace(now, power2, QType(dr2.d_type), records, signatures, authRecs, true, boost::none);
373 records.clear();
374 BOOST_CHECK_EQUAL(MRC.size(), 2);
375
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 */
379 MRC.doPrune(1);
380 BOOST_CHECK_EQUAL(MRC.size(), 1);
381
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);
389
390 /* clear everything up */
391 MRC.doWipeCache(DNSName("."), true);
392 BOOST_CHECK_EQUAL(MRC.size(), 0);
393 records.clear();
394
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);
398 records.clear();
399 records.push_back(dr2);
400 MRC.replace(now, power2, QType(dr2.d_type), records, signatures, authRecs, true, boost::none);
401 records.clear();
402 BOOST_CHECK_EQUAL(MRC.size(), 2);
403
404 /* trigger a miss (expired) for power2 */
405 BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), false, &retrieved, who, nullptr), -now);
406
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 */
410 MRC.doPrune(1);
411 BOOST_CHECK_EQUAL(MRC.size(), 1);
412
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);
420 }
421
422 BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries) {
423 MemRecursorCache MRC;
424
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");
435
436 /* entry for power, which will expire in 30s */
437 DNSRecord dr1;
438 ComboAddress dr1Content("2001:DB8::1");
439 dr1.d_name = power1;
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;
445
446 /* entry for power1, which will expire in 30s too */
447 DNSRecord dr2;
448 ComboAddress dr2Content("2001:DB8::2");
449 dr2.d_name = power2;
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;
455
456 /* insert both entries */
457 records.push_back(dr1);
458 MRC.replace(now, power1, QType(dr1.d_type), records, signatures, authRecs, true, boost::none);
459 records.clear();
460 records.push_back(dr2);
461 MRC.replace(now, power2, QType(dr2.d_type), records, signatures, authRecs, true, boost::none);
462 records.clear();
463 BOOST_CHECK_EQUAL(MRC.size(), 2);
464
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 */
468 MRC.doPrune(1);
469 BOOST_CHECK_EQUAL(MRC.size(), 1);
470
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);
477
478 /* clear everything up */
479 MRC.doWipeCache(DNSName("."), true);
480 BOOST_CHECK_EQUAL(MRC.size(), 0);
481 records.clear();
482
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);
486 records.clear();
487 records.push_back(dr2);
488 MRC.replace(now, power2, QType(dr2.d_type), records, signatures, authRecs, true, boost::none);
489 records.clear();
490 BOOST_CHECK_EQUAL(MRC.size(), 2);
491
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);
495 records.clear();
496 BOOST_CHECK_EQUAL(MRC.size(), 2);
497
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 */
502 MRC.doPrune(1);
503 BOOST_CHECK_EQUAL(MRC.size(), 1);
504
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);
511
512 /* clear everything up */
513 MRC.doWipeCache(DNSName("."), true);
514 BOOST_CHECK_EQUAL(MRC.size(), 0);
515 records.clear();
516
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);
520 records.clear();
521 records.push_back(dr2);
522 MRC.replace(now, power2, QType(dr2.d_type), records, signatures, authRecs, true, boost::none);
523 records.clear();
524 BOOST_CHECK_EQUAL(MRC.size(), 2);
525
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());
530
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 */
534 MRC.doPrune(1);
535 BOOST_CHECK_EQUAL(MRC.size(), 1);
536
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);
543
544 MRC.doPrune(0);
545 BOOST_CHECK_EQUAL(MRC.size(), 0);
546
547 /* add a lot of netmask-specific entries */
548 for (size_t i = 0; i <= 255; i++) {
549 records.clear();
550
551 DNSRecord r1;
552 ComboAddress r1Content("192.0.2." + std::to_string(i));
553 r1.d_name = power1;
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);
560
561 MRC.replace(now, power1, QType(QType::A), records, signatures, authRecs, true, Netmask(r1Content, 32));
562 }
563
564 BOOST_CHECK_EQUAL(MRC.size(), 256);
565 BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1);
566
567 /* remove a bit less than half of them */
568 size_t keep = 129;
569 MRC.doPrune(keep);
570 BOOST_CHECK_EQUAL(MRC.size(), keep);
571 BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1);
572
573 /* check that we can still retrieve the remaining ones */
574 size_t found = 0;
575 for (size_t i = 0; i <= 255; i++) {
576 retrieved.clear();
577 ComboAddress whoLoop("192.0.2." + std::to_string(i));
578
579 auto ret = MRC.get(now, power1, QType(QType::A), false, &retrieved, whoLoop);
580 if (ret > 0) {
581 BOOST_REQUIRE_EQUAL(retrieved.size(), 1);
582 BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), whoLoop.toString());
583 found++;
584 }
585 else {
586 BOOST_REQUIRE_EQUAL(ret, -1);
587 BOOST_REQUIRE_EQUAL(retrieved.size(), 0);
588 }
589 }
590
591 BOOST_CHECK_EQUAL(found, keep);
592
593 /* remove the rest */
594 MRC.doPrune(0);
595 BOOST_CHECK_EQUAL(MRC.size(), 0);
596 BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0);
597 }
598
599 BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex) {
600 MemRecursorCache MRC;
601
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");
609
610 time_t ttl = 10;
611 time_t ttd = now + ttl;
612 DNSRecord dr1;
613 ComboAddress dr1Content("192.0.2.255");
614 dr1.d_name = power;
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;
620
621 DNSRecord dr2;
622 ComboAddress dr2Content("192.0.2.127");
623 dr2.d_name = power;
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;
629
630 BOOST_CHECK_EQUAL(MRC.size(), 0);
631 BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0);
632
633 /* no entry in the ECS index, no non-specific entry either */
634 retrieved.clear();
635 BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, who), -1);
636
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);
640
641 BOOST_CHECK_EQUAL(MRC.size(), 1);
642 BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0);
643
644 /* retrieve the non-specific entry */
645 retrieved.clear();
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());
649
650 /* wipe everything */
651 MRC.doPrune(0);
652 BOOST_CHECK_EQUAL(MRC.size(), 0);
653 BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0);
654
655 /* insert a specific entry */
656 MRC.replace(now, power, QType(QType::A), records, signatures, authRecords, true, Netmask("192.0.2.0/31"));
657
658 BOOST_CHECK_EQUAL(MRC.size(), 1);
659 BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1);
660
661 /* there is an ECS index for that entry but no match, and no non-specific entry */
662 retrieved.clear();
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);
665
666 /* there is an ECS index for that entry and we get a match */
667 retrieved.clear();
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());
671
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 */
674 retrieved.clear();
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);
677
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);
681
682 /* wipe everything */
683 MRC.doPrune(0);
684 BOOST_CHECK_EQUAL(MRC.size(), 0);
685 BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0);
686
687 /* insert a specific entry */
688 MRC.replace(now, power, QType(QType::A), records, signatures, authRecords, true, Netmask("192.0.2.0/24"));
689
690 BOOST_CHECK_EQUAL(MRC.size(), 1);
691 BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1);
692
693 /* insert a slightly more specific one, but expiring sooner */
694 records.clear();
695 records.push_back(dr2);
696 MRC.replace(now, power, QType(QType::A), records, signatures, authRecords, true, Netmask("192.0.2.0/26"));
697
698 BOOST_CHECK_EQUAL(MRC.size(), 2);
699 BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1);
700
701 /* check that we get the most specific one as long as it's still valid */
702 retrieved.clear();
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());
706
707 /* there is an ECS index for that entry and we get a match,
708 but it has expired.
709 The second ECS is a match too, and is valid. */
710 retrieved.clear();
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());
714
715 /* The ECS index should not be empty */
716 BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1);
717 BOOST_CHECK_EQUAL(MRC.size(), 2);
718
719 /* wipe everything */
720 MRC.doPrune(0);
721 BOOST_CHECK_EQUAL(MRC.size(), 0);
722 BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0);
723
724 /* insert a non-specific entry */
725 records.clear();
726 records.push_back(dr1);
727 MRC.replace(now, power, QType(QType::A), records, signatures, authRecords, true, boost::none);
728
729 BOOST_CHECK_EQUAL(MRC.size(), 1);
730 BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0);
731
732 /* insert a subnet-specific entry */
733 records.clear();
734 records.push_back(dr2);
735 MRC.replace(now, power, QType(QType::A), records, signatures, authRecords, true, Netmask("192.0.2.42/32"));
736
737 BOOST_CHECK_EQUAL(MRC.size(), 2);
738 BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1);
739
740 /* there is an ECS index for that entry and it doesn't match. No other match, but we have a non-specific entry */
741 retrieved.clear();
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());
745
746 BOOST_CHECK_EQUAL(MRC.size(), 2);
747 BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1);
748
749 /* wipe everything */
750 MRC.doPrune(0);
751 BOOST_CHECK_EQUAL(MRC.size(), 0);
752 BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0);
753 }
754
755 BOOST_AUTO_TEST_SUITE_END()