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