1 #define BOOST_TEST_DYN_LINK
2 #define BOOST_TEST_NO_MAIN
3
4 #include <boost/test/unit_test.hpp>
5
6 #include "ednscookies.hh"
7 #include "ednsoptions.hh"
8 #include "ednssubnet.hh"
9 #include "dnsdist.hh"
10 #include "iputils.hh"
11 #include "dnswriter.hh"
12 #include "dnsdist-cache.hh"
13 #include "gettime.hh"
14 #include "packetcache.hh"
15
16 BOOST_AUTO_TEST_SUITE(test_dnsdistpacketcache_cc)
17
BOOST_AUTO_TEST_CASE(test_PacketCacheSimple)18 BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) {
19 const size_t maxEntries = 150000;
20 DNSDistPacketCache PC(maxEntries, 86400, 1);
21 BOOST_CHECK_EQUAL(PC.getSize(), 0U);
22 struct timespec queryTime;
23 gettime(&queryTime); // does not have to be accurate ("realTime") in tests
24
25 size_t counter=0;
26 size_t skipped=0;
27 ComboAddress remote;
28 bool dnssecOK = false;
29 const time_t now = time(nullptr);
30 try {
31 for (counter = 0; counter < 100000; ++counter) {
32 DNSName a=DNSName(std::to_string(counter))+DNSName(" hello");
33 BOOST_CHECK_EQUAL(DNSName(a.toString()), a);
34
35 PacketBuffer query;
36 GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::A, QClass::IN, 0);
37 pwQ.getHeader()->rd = 1;
38
39 PacketBuffer response;
40 GenericDNSPacketWriter<PacketBuffer> pwR(response, a, QType::A, QClass::IN, 0);
41 pwR.getHeader()->rd = 1;
42 pwR.getHeader()->ra = 1;
43 pwR.getHeader()->qr = 1;
44 pwR.getHeader()->id = pwQ.getHeader()->id;
45 pwR.startRecord(a, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
46 pwR.xfr32BitInt(0x01020304);
47 pwR.commit();
48
49 uint32_t key = 0;
50 boost::optional<Netmask> subnet;
51 DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, query, false, &queryTime);
52 bool found = PC.get(dq, 0, &key, subnet, dnssecOK);
53 BOOST_CHECK_EQUAL(found, false);
54 BOOST_CHECK(!subnet);
55
56 PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::A, QClass::IN, response, false, 0, boost::none);
57
58 found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, 0, true);
59 if (found == true) {
60 BOOST_CHECK_EQUAL(dq.getData().size(), response.size());
61 int match = memcmp(dq.getData().data(), response.data(), dq.getData().size());
62 BOOST_CHECK_EQUAL(match, 0);
63 BOOST_CHECK(!subnet);
64 }
65 else {
66 skipped++;
67 }
68 }
69
70 BOOST_CHECK_EQUAL(skipped, PC.getInsertCollisions());
71 BOOST_CHECK_EQUAL(PC.getSize(), counter - skipped);
72
73 size_t deleted=0;
74 size_t delcounter=0;
75 for (delcounter=0; delcounter < counter/1000; ++delcounter) {
76 DNSName a=DNSName(std::to_string(delcounter))+DNSName(" hello");
77 PacketBuffer query;
78 GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::A, QClass::IN, 0);
79 pwQ.getHeader()->rd = 1;
80 uint32_t key = 0;
81 boost::optional<Netmask> subnet;
82 DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, query, false, &queryTime);
83 bool found = PC.get(dq, 0, &key, subnet, dnssecOK);
84 if (found == true) {
85 auto removed = PC.expungeByName(a);
86 BOOST_CHECK_EQUAL(removed, 1U);
87 deleted += removed;
88 }
89 }
90 BOOST_CHECK_EQUAL(PC.getSize(), counter - skipped - deleted);
91
92 size_t matches=0;
93 size_t expected=counter-skipped-deleted;
94 for (; delcounter < counter; ++delcounter) {
95 DNSName a(DNSName(std::to_string(delcounter))+DNSName(" hello"));
96 PacketBuffer query;
97 GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::A, QClass::IN, 0);
98 pwQ.getHeader()->rd = 1;
99 uint32_t key = 0;
100 boost::optional<Netmask> subnet;
101 DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, query, false, &queryTime);
102 if (PC.get(dq, pwQ.getHeader()->id, &key, subnet, dnssecOK)) {
103 matches++;
104 }
105 }
106
107 /* in the unlikely event that the test took so long that the entries did expire.. */
108 auto expired = PC.purgeExpired(0, now);
109 BOOST_CHECK_EQUAL(matches + expired, expected);
110
111 auto remaining = PC.getSize();
112 auto removed = PC.expungeByName(DNSName(" hello"), QType::ANY, true);
113 BOOST_CHECK_EQUAL(PC.getSize(), 0U);
114 BOOST_CHECK_EQUAL(removed, remaining);
115
116 /* nothing to remove */
117 BOOST_CHECK_EQUAL(PC.purgeExpired(0, now), 0U);
118 }
119 catch (const PDNSException& e) {
120 cerr<<"Had error: "<<e.reason<<endl;
121 throw;
122 }
123 }
124
BOOST_AUTO_TEST_CASE(test_PacketCacheSharded)125 BOOST_AUTO_TEST_CASE(test_PacketCacheSharded) {
126 const size_t maxEntries = 150000;
127 const size_t numberOfShards = 10;
128 DNSDistPacketCache PC(maxEntries, 86400, 1, 60, 3600, 60, false, numberOfShards);
129 BOOST_CHECK_EQUAL(PC.getSize(), 0U);
130 struct timespec queryTime;
131 gettime(&queryTime); // does not have to be accurate ("realTime") in tests
132
133 size_t counter = 0;
134 size_t skipped = 0;
135 ComboAddress remote;
136 bool dnssecOK = false;
137 const time_t now = time(nullptr);
138
139 try {
140 for (counter = 0; counter < 100000; ++counter) {
141 DNSName a(std::to_string(counter) + ".powerdns.com.");
142
143 PacketBuffer query;
144 GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::AAAA, QClass::IN, 0);
145 pwQ.getHeader()->rd = 1;
146
147 PacketBuffer response;
148 GenericDNSPacketWriter<PacketBuffer> pwR(response, a, QType::AAAA, QClass::IN, 0);
149 pwR.getHeader()->rd = 1;
150 pwR.getHeader()->ra = 1;
151 pwR.getHeader()->qr = 1;
152 pwR.getHeader()->id = pwQ.getHeader()->id;
153 pwR.startRecord(a, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
154 ComboAddress v6("2001:db8::1");
155 pwR.xfrIP6(std::string(reinterpret_cast<const char*>(v6.sin6.sin6_addr.s6_addr), 16));
156 pwR.xfr32BitInt(0x01020304);
157 pwR.commit();
158
159 uint32_t key = 0;
160 boost::optional<Netmask> subnet;
161 DNSQuestion dq(&a, QType::AAAA, QClass::IN, &remote, &remote, query, false, &queryTime);
162 bool found = PC.get(dq, 0, &key, subnet, dnssecOK);
163 BOOST_CHECK_EQUAL(found, false);
164 BOOST_CHECK(!subnet);
165
166 PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::AAAA, QClass::IN, response, false, 0, boost::none);
167
168 found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, 0, true);
169 if (found == true) {
170 BOOST_CHECK_EQUAL(dq.getData().size(), response.size());
171 int match = memcmp(dq.getData().data(), response.data(), dq.getData().size());
172 BOOST_CHECK_EQUAL(match, 0);
173 BOOST_CHECK(!subnet);
174 }
175 else {
176 skipped++;
177 }
178 }
179
180 BOOST_CHECK_EQUAL(skipped, PC.getInsertCollisions());
181 BOOST_CHECK_EQUAL(PC.getSize(), counter - skipped);
182
183 size_t matches = 0;
184 for (counter = 0; counter < 100000; ++counter) {
185 DNSName a(std::to_string(counter) + ".powerdns.com.");
186
187 PacketBuffer query;
188 GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::AAAA, QClass::IN, 0);
189 pwQ.getHeader()->rd = 1;
190 uint32_t key = 0;
191 boost::optional<Netmask> subnet;
192 DNSQuestion dq(&a, QType::AAAA, QClass::IN, &remote, &remote, query, false, &queryTime);
193 if (PC.get(dq, pwQ.getHeader()->id, &key, subnet, dnssecOK)) {
194 matches++;
195 }
196 }
197
198 BOOST_CHECK_EQUAL(matches, counter - skipped);
199
200 auto remaining = PC.getSize();
201
202 /* no entry should have expired */
203 auto expired = PC.purgeExpired(0, now);
204 BOOST_CHECK_EQUAL(expired, 0U);
205
206 /* but after the TTL .. let's ask for at most 1k entries */
207 auto removed = PC.purgeExpired(1000, now + 7200 + 3600);
208 BOOST_CHECK_EQUAL(removed, remaining - 1000U);
209 BOOST_CHECK_EQUAL(PC.getSize(), 1000U);
210
211 /* now remove everything */
212 removed = PC.purgeExpired(0, now + 7200 + 3600);
213 BOOST_CHECK_EQUAL(removed, 1000U);
214 BOOST_CHECK_EQUAL(PC.getSize(), 0U);
215
216 /* nothing to remove */
217 BOOST_CHECK_EQUAL(PC.purgeExpired(0, now), 0U);
218 }
219 catch (const PDNSException& e) {
220 cerr<<"Had error: "<<e.reason<<endl;
221 throw;
222 }
223 }
224
BOOST_AUTO_TEST_CASE(test_PacketCacheServFailTTL)225 BOOST_AUTO_TEST_CASE(test_PacketCacheServFailTTL) {
226 const size_t maxEntries = 150000;
227 DNSDistPacketCache PC(maxEntries, 86400, 1);
228 struct timespec queryTime;
229 gettime(&queryTime); // does not have to be accurate ("realTime") in tests
230
231 ComboAddress remote;
232 bool dnssecOK = false;
233 try {
234 DNSName a = DNSName("servfail");
235 BOOST_CHECK_EQUAL(DNSName(a.toString()), a);
236
237 PacketBuffer query;
238 GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::A, QClass::IN, 0);
239 pwQ.getHeader()->rd = 1;
240
241 PacketBuffer response;
242 GenericDNSPacketWriter<PacketBuffer> pwR(response, a, QType::A, QClass::IN, 0);
243 pwR.getHeader()->rd = 1;
244 pwR.getHeader()->ra = 0;
245 pwR.getHeader()->qr = 1;
246 pwR.getHeader()->rcode = RCode::ServFail;
247 pwR.getHeader()->id = pwQ.getHeader()->id;
248 pwR.commit();
249
250 uint32_t key = 0;
251 boost::optional<Netmask> subnet;
252 DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, query, false, &queryTime);
253 bool found = PC.get(dq, 0, &key, subnet, dnssecOK);
254 BOOST_CHECK_EQUAL(found, false);
255 BOOST_CHECK(!subnet);
256
257 // Insert with failure-TTL of 0 (-> should not enter cache).
258 PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::A, QClass::IN, response, false, RCode::ServFail, boost::optional<uint32_t>(0));
259 found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, 0, true);
260 BOOST_CHECK_EQUAL(found, false);
261 BOOST_CHECK(!subnet);
262
263 // Insert with failure-TTL non-zero (-> should enter cache).
264 PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::A, QClass::IN, response, false, RCode::ServFail, boost::optional<uint32_t>(300));
265 found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, 0, true);
266 BOOST_CHECK_EQUAL(found, true);
267 BOOST_CHECK(!subnet);
268 }
269 catch(PDNSException& e) {
270 cerr<<"Had error: "<<e.reason<<endl;
271 throw;
272 }
273 }
274
BOOST_AUTO_TEST_CASE(test_PacketCacheNoDataTTL)275 BOOST_AUTO_TEST_CASE(test_PacketCacheNoDataTTL) {
276 const size_t maxEntries = 150000;
277 DNSDistPacketCache PC(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
278
279 struct timespec queryTime;
280 gettime(&queryTime); // does not have to be accurate ("realTime") in tests
281
282 ComboAddress remote;
283 bool dnssecOK = false;
284 try {
285 DNSName name("nodata");
286 PacketBuffer query;
287 GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
288 pwQ.getHeader()->rd = 1;
289
290 PacketBuffer response;
291 GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
292 pwR.getHeader()->rd = 1;
293 pwR.getHeader()->ra = 0;
294 pwR.getHeader()->qr = 1;
295 pwR.getHeader()->rcode = RCode::NoError;
296 pwR.getHeader()->id = pwQ.getHeader()->id;
297 pwR.commit();
298 pwR.startRecord(name, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
299 pwR.commit();
300 pwR.addOpt(4096, 0, 0);
301 pwR.commit();
302
303 uint32_t key = 0;
304 boost::optional<Netmask> subnet;
305 DNSQuestion dq(&name, QType::A, QClass::IN, &remote, &remote, query, false, &queryTime);
306 bool found = PC.get(dq, 0, &key, subnet, dnssecOK);
307 BOOST_CHECK_EQUAL(found, false);
308 BOOST_CHECK(!subnet);
309
310 PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, name, QType::A, QClass::IN, response, false, RCode::NoError, boost::none);
311 found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, 0, true);
312 BOOST_CHECK_EQUAL(found, true);
313 BOOST_CHECK(!subnet);
314
315 sleep(2);
316 /* it should have expired by now */
317 found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, 0, true);
318 BOOST_CHECK_EQUAL(found, false);
319 BOOST_CHECK(!subnet);
320 }
321 catch(const PDNSException& e) {
322 cerr<<"Had error: "<<e.reason<<endl;
323 throw;
324 }
325 }
326
BOOST_AUTO_TEST_CASE(test_PacketCacheNXDomainTTL)327 BOOST_AUTO_TEST_CASE(test_PacketCacheNXDomainTTL) {
328 const size_t maxEntries = 150000;
329 DNSDistPacketCache PC(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
330
331 struct timespec queryTime;
332 gettime(&queryTime); // does not have to be accurate ("realTime") in tests
333
334 ComboAddress remote;
335 bool dnssecOK = false;
336 try {
337 DNSName name("nxdomain");
338 PacketBuffer query;
339 GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
340 pwQ.getHeader()->rd = 1;
341
342 PacketBuffer response;
343 GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
344 pwR.getHeader()->rd = 1;
345 pwR.getHeader()->ra = 0;
346 pwR.getHeader()->qr = 1;
347 pwR.getHeader()->rcode = RCode::NXDomain;
348 pwR.getHeader()->id = pwQ.getHeader()->id;
349 pwR.commit();
350 pwR.startRecord(name, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
351 pwR.commit();
352 pwR.addOpt(4096, 0, 0);
353 pwR.commit();
354
355 uint32_t key = 0;
356 boost::optional<Netmask> subnet;
357 DNSQuestion dq(&name, QType::A, QClass::IN, &remote, &remote, query, false, &queryTime);
358 bool found = PC.get(dq, 0, &key, subnet, dnssecOK);
359 BOOST_CHECK_EQUAL(found, false);
360 BOOST_CHECK(!subnet);
361
362 PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, name, QType::A, QClass::IN, response, false, RCode::NXDomain, boost::none);
363 found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, 0, true);
364 BOOST_CHECK_EQUAL(found, true);
365 BOOST_CHECK(!subnet);
366
367 sleep(2);
368 /* it should have expired by now */
369 found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, 0, true);
370 BOOST_CHECK_EQUAL(found, false);
371 BOOST_CHECK(!subnet);
372 }
373 catch(const PDNSException& e) {
374 cerr<<"Had error: "<<e.reason<<endl;
375 throw;
376 }
377 }
378
379 static DNSDistPacketCache g_PC(500000);
380
threadMangler(unsigned int offset)381 static void threadMangler(unsigned int offset)
382 {
383 struct timespec queryTime;
384 gettime(&queryTime); // does not have to be accurate ("realTime") in tests
385 try {
386 ComboAddress remote;
387 bool dnssecOK = false;
388 for(unsigned int counter=0; counter < 100000; ++counter) {
389 DNSName a=DNSName("hello ")+DNSName(std::to_string(counter+offset));
390 PacketBuffer query;
391 GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::A, QClass::IN, 0);
392 pwQ.getHeader()->rd = 1;
393
394 PacketBuffer response;
395 GenericDNSPacketWriter<PacketBuffer> pwR(response, a, QType::A, QClass::IN, 0);
396 pwR.getHeader()->rd = 1;
397 pwR.getHeader()->ra = 1;
398 pwR.getHeader()->qr = 1;
399 pwR.getHeader()->id = pwQ.getHeader()->id;
400 pwR.startRecord(a, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER);
401 pwR.xfr32BitInt(0x01020304);
402 pwR.commit();
403
404 uint32_t key = 0;
405 boost::optional<Netmask> subnet;
406 DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, query, false, &queryTime);
407 g_PC.get(dq, 0, &key, subnet, dnssecOK);
408
409 g_PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::A, QClass::IN, response, false, 0, boost::none);
410 }
411 }
412 catch(PDNSException& e) {
413 cerr<<"Had error: "<<e.reason<<endl;
414 throw;
415 }
416 }
417
418 AtomicCounter g_missing;
419
threadReader(unsigned int offset)420 static void threadReader(unsigned int offset)
421 {
422 bool dnssecOK = false;
423 struct timespec queryTime;
424 gettime(&queryTime); // does not have to be accurate ("realTime") in tests
425 try
426 {
427 ComboAddress remote;
428 for(unsigned int counter=0; counter < 100000; ++counter) {
429 DNSName a=DNSName("hello ")+DNSName(std::to_string(counter+offset));
430 PacketBuffer query;
431 GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::A, QClass::IN, 0);
432 pwQ.getHeader()->rd = 1;
433
434 uint32_t key = 0;
435 boost::optional<Netmask> subnet;
436 DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, query, false, &queryTime);
437 bool found = g_PC.get(dq, 0, &key, subnet, dnssecOK);
438 if (!found) {
439 g_missing++;
440 }
441 }
442 }
443 catch(PDNSException& e) {
444 cerr<<"Had error in threadReader: "<<e.reason<<endl;
445 throw;
446 }
447 }
448
BOOST_AUTO_TEST_CASE(test_PacketCacheThreaded)449 BOOST_AUTO_TEST_CASE(test_PacketCacheThreaded) {
450 try {
451 std::vector<std::thread> threads;
452 for (int i = 0; i < 4; ++i) {
453 threads.push_back(std::thread(threadMangler, i*1000000UL));
454 }
455
456 for (auto& t : threads) {
457 t.join();
458 }
459
460 threads.clear();
461
462 BOOST_CHECK_EQUAL(g_PC.getSize() + g_PC.getDeferredInserts() + g_PC.getInsertCollisions(), 400000U);
463 BOOST_CHECK_SMALL(1.0*g_PC.getInsertCollisions(), 10000.0);
464
465 for (int i = 0; i < 4; ++i) {
466 threads.push_back(std::thread(threadReader, i*1000000UL));
467 }
468
469 for (auto& t : threads) {
470 t.join();
471 }
472
473 BOOST_CHECK((g_PC.getDeferredInserts() + g_PC.getDeferredLookups() + g_PC.getInsertCollisions()) >= g_missing);
474 }
475 catch(PDNSException& e) {
476 cerr<<"Had error: "<<e.reason<<endl;
477 throw;
478 }
479
480 }
481
BOOST_AUTO_TEST_CASE(test_PCCollision)482 BOOST_AUTO_TEST_CASE(test_PCCollision) {
483 const size_t maxEntries = 150000;
484 DNSDistPacketCache PC(maxEntries, 86400, 1, 60, 3600, 60, false, 1, true, true);
485 BOOST_CHECK_EQUAL(PC.getSize(), 0U);
486
487 DNSName qname("www.powerdns.com.");
488 uint16_t qtype = QType::AAAA;
489 uint16_t qid = 0x42;
490 uint32_t key;
491 uint32_t secondKey;
492 boost::optional<Netmask> subnetOut;
493 bool dnssecOK = false;
494
495 /* lookup for a query with a first ECS value,
496 insert a corresponding response */
497 {
498 PacketBuffer query;
499 GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, qtype, QClass::IN, 0);
500 pwQ.getHeader()->rd = 1;
501 pwQ.getHeader()->id = qid;
502 GenericDNSPacketWriter<PacketBuffer>::optvect_t ednsOptions;
503 EDNSSubnetOpts opt;
504 opt.source = Netmask("10.0.59.220/32");
505 ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
506 pwQ.addOpt(512, 0, 0, ednsOptions);
507 pwQ.commit();
508
509 ComboAddress remote("192.0.2.1");
510 struct timespec queryTime;
511 gettime(&queryTime);
512 DNSQuestion dq(&qname, QType::AAAA, QClass::IN, &remote, &remote, query, false, &queryTime);
513 bool found = PC.get(dq, 0, &key, subnetOut, dnssecOK);
514 BOOST_CHECK_EQUAL(found, false);
515 BOOST_REQUIRE(subnetOut);
516 BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
517
518 PacketBuffer response;
519 GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, qtype, QClass::IN, 0);
520 pwR.getHeader()->rd = 1;
521 pwR.getHeader()->id = qid;
522 pwR.startRecord(qname, qtype, 100, QClass::IN, DNSResourceRecord::ANSWER);
523 ComboAddress v6("::1");
524 pwR.xfrCAWithoutPort(6, v6);
525 pwR.commit();
526 pwR.addOpt(512, 0, 0, ednsOptions);
527 pwR.commit();
528
529 PC.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), dnssecOK, qname, qtype, QClass::IN, response, false, RCode::NoError, boost::none);
530 BOOST_CHECK_EQUAL(PC.getSize(), 1U);
531
532 found = PC.get(dq, 0, &key, subnetOut, dnssecOK);
533 BOOST_CHECK_EQUAL(found, true);
534 BOOST_REQUIRE(subnetOut);
535 BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
536 }
537
538 /* now lookup for the same query with a different ECS value,
539 we should get the same key (collision) but no match */
540 {
541 PacketBuffer query;
542 GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, qtype, QClass::IN, 0);
543 pwQ.getHeader()->rd = 1;
544 pwQ.getHeader()->id = qid;
545 GenericDNSPacketWriter<PacketBuffer>::optvect_t ednsOptions;
546 EDNSSubnetOpts opt;
547 opt.source = Netmask("10.0.167.48/32");
548 ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
549 pwQ.addOpt(512, 0, 0, ednsOptions);
550 pwQ.commit();
551
552 ComboAddress remote("192.0.2.1");
553 struct timespec queryTime;
554 gettime(&queryTime);
555 DNSQuestion dq(&qname, QType::AAAA, QClass::IN, &remote, &remote, query, false, &queryTime);
556 bool found = PC.get(dq, 0, &secondKey, subnetOut, dnssecOK);
557 BOOST_CHECK_EQUAL(found, false);
558 BOOST_CHECK_EQUAL(secondKey, key);
559 BOOST_REQUIRE(subnetOut);
560 BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
561 BOOST_CHECK_EQUAL(PC.getLookupCollisions(), 1U);
562 }
563
564 #if 0
565 /* to be able to compute a new collision if the packet cache hashing code is updated */
566 {
567 DNSDistPacketCache pc(10000);
568 GenericDNSPacketWriter<PacketBuffer>::optvect_t ednsOptions;
569 EDNSSubnetOpts opt;
570 std::map<uint32_t, Netmask> colMap;
571 size_t collisions = 0;
572 size_t total = 0;
573 //qname = DNSName("collision-with-ecs-parsing.cache.tests.powerdns.com.");
574
575 for (size_t idxA = 0; idxA < 256; idxA++) {
576 for (size_t idxB = 0; idxB < 256; idxB++) {
577 for (size_t idxC = 0; idxC < 256; idxC++) {
578 PacketBuffer secondQuery;
579 GenericDNSPacketWriter<PacketBuffer> pwFQ(secondQuery, qname, QType::AAAA, QClass::IN, 0);
580 pwFQ.getHeader()->rd = 1;
581 pwFQ.getHeader()->qr = false;
582 pwFQ.getHeader()->id = 0x42;
583 opt.source = Netmask("10." + std::to_string(idxA) + "." + std::to_string(idxB) + "." + std::to_string(idxC) + "/32");
584 ednsOptions.clear();
585 ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
586 pwFQ.addOpt(512, 0, 0, ednsOptions);
587 pwFQ.commit();
588 secondKey = pc.getKey(qname.toDNSString(), qname.wirelength(), secondQuery, false);
589 auto pair = colMap.insert(std::make_pair(secondKey, opt.source));
590 total++;
591 if (!pair.second) {
592 collisions++;
593 cerr<<"Collision between "<<colMap[secondKey].toString()<<" and "<<opt.source.toString()<<" for key "<<secondKey<<endl;
594 goto done;
595 }
596 }
597 }
598 }
599 done:
600 cerr<<"collisions: "<<collisions<<endl;
601 cerr<<"total: "<<total<<endl;
602 }
603 #endif
604 }
605
BOOST_AUTO_TEST_CASE(test_PCDNSSECCollision)606 BOOST_AUTO_TEST_CASE(test_PCDNSSECCollision) {
607 const size_t maxEntries = 150000;
608 DNSDistPacketCache PC(maxEntries, 86400, 1, 60, 3600, 60, false, 1, true, true);
609 BOOST_CHECK_EQUAL(PC.getSize(), 0U);
610
611 DNSName qname("www.powerdns.com.");
612 uint16_t qtype = QType::AAAA;
613 uint16_t qid = 0x42;
614 uint32_t key;
615 boost::optional<Netmask> subnetOut;
616
617 /* lookup for a query with DNSSEC OK,
618 insert a corresponding response with DO set,
619 check that it doesn't match without DO, but does with it */
620 {
621 PacketBuffer query;
622 GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, qtype, QClass::IN, 0);
623 pwQ.getHeader()->rd = 1;
624 pwQ.getHeader()->id = qid;
625 pwQ.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
626 pwQ.commit();
627
628 ComboAddress remote("192.0.2.1");
629 struct timespec queryTime;
630 gettime(&queryTime);
631 DNSQuestion dq(&qname, QType::AAAA, QClass::IN, &remote, &remote, query, false, &queryTime);
632 bool found = PC.get(dq, 0, &key, subnetOut, true);
633 BOOST_CHECK_EQUAL(found, false);
634
635 PacketBuffer response;
636 GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, qtype, QClass::IN, 0);
637 pwR.getHeader()->rd = 1;
638 pwR.getHeader()->id = qid;
639 pwR.startRecord(qname, qtype, 100, QClass::IN, DNSResourceRecord::ANSWER);
640 ComboAddress v6("::1");
641 pwR.xfrCAWithoutPort(6, v6);
642 pwR.commit();
643 pwR.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
644 pwR.commit();
645
646 PC.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), /* DNSSEC OK is set */ true, qname, qtype, QClass::IN, response, false, RCode::NoError, boost::none);
647 BOOST_CHECK_EQUAL(PC.getSize(), 1U);
648
649 found = PC.get(dq, 0, &key, subnetOut, false);
650 BOOST_CHECK_EQUAL(found, false);
651
652 found = PC.get(dq, 0, &key, subnetOut, true);
653 BOOST_CHECK_EQUAL(found, true);
654 }
655
656 }
657
658 BOOST_AUTO_TEST_SUITE_END()
659