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