]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/test-dnsdistpacketcache_cc.cc
dnsdist: Set a correct EDNS OPT RR for self-generated answers
[thirdparty/pdns.git] / pdns / test-dnsdistpacketcache_cc.cc
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
15 BOOST_AUTO_TEST_SUITE(dnsdistpacketcache_cc)
16
17 BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) {
18 const size_t maxEntries = 150000;
19 DNSDistPacketCache PC(maxEntries, 86400, 1);
20 BOOST_CHECK_EQUAL(PC.getSize(), 0);
21 struct timespec queryTime;
22 gettime(&queryTime); // does not have to be accurate ("realTime") in tests
23
24 size_t counter=0;
25 size_t skipped=0;
26 ComboAddress remote;
27 try {
28 for(counter = 0; counter < 100000; ++counter) {
29 DNSName a=DNSName(std::to_string(counter))+DNSName(" hello");
30 BOOST_CHECK_EQUAL(DNSName(a.toString()), a);
31
32 vector<uint8_t> query;
33 DNSPacketWriter pwQ(query, a, QType::A, QClass::IN, 0);
34 pwQ.getHeader()->rd = 1;
35
36 vector<uint8_t> response;
37 DNSPacketWriter pwR(response, a, QType::A, QClass::IN, 0);
38 pwR.getHeader()->rd = 1;
39 pwR.getHeader()->ra = 1;
40 pwR.getHeader()->qr = 1;
41 pwR.getHeader()->id = pwQ.getHeader()->id;
42 pwR.startRecord(a, QType::A, 100, QClass::IN, DNSResourceRecord::ANSWER);
43 pwR.xfr32BitInt(0x01020304);
44 pwR.commit();
45 uint16_t responseLen = response.size();
46
47 char responseBuf[4096];
48 uint16_t responseBufSize = sizeof(responseBuf);
49 uint32_t key = 0;
50 boost::optional<Netmask> subnet;
51 auto dh = reinterpret_cast<dnsheader*>(query.data());
52 DNSQuestion dq(&a, QType::A, QClass::IN, 0, &remote, &remote, dh, query.size(), query.size(), false, &queryTime);
53 bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet);
54 BOOST_CHECK_EQUAL(found, false);
55 BOOST_CHECK(!subnet);
56
57 PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, 0, boost::none);
58
59 found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true);
60 if (found == true) {
61 BOOST_CHECK_EQUAL(responseBufSize, responseLen);
62 int match = memcmp(responseBuf, response.data(), responseLen);
63 BOOST_CHECK_EQUAL(match, 0);
64 BOOST_CHECK(!subnet);
65 }
66 else {
67 skipped++;
68 }
69 }
70
71 BOOST_CHECK_EQUAL(skipped, PC.getInsertCollisions());
72 BOOST_CHECK_EQUAL(PC.getSize(), counter - skipped);
73
74 size_t deleted=0;
75 size_t delcounter=0;
76 for(delcounter=0; delcounter < counter/1000; ++delcounter) {
77 DNSName a=DNSName(std::to_string(delcounter))+DNSName(" hello");
78 vector<uint8_t> query;
79 DNSPacketWriter pwQ(query, a, QType::A, QClass::IN, 0);
80 pwQ.getHeader()->rd = 1;
81 char responseBuf[4096];
82 uint16_t responseBufSize = sizeof(responseBuf);
83 uint32_t key = 0;
84 boost::optional<Netmask> subnet;
85 DNSQuestion dq(&a, QType::A, QClass::IN, 0, &remote, &remote, (struct dnsheader*) query.data(), query.size(), query.size(), false, &queryTime);
86 bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet);
87 if (found == true) {
88 PC.expungeByName(a);
89 deleted++;
90 }
91 }
92 BOOST_CHECK_EQUAL(PC.getSize(), counter - skipped - deleted);
93
94
95 size_t matches=0;
96 vector<DNSResourceRecord> entry;
97 size_t expected=counter-skipped-deleted;
98 for(; delcounter < counter; ++delcounter) {
99 DNSName a(DNSName(std::to_string(delcounter))+DNSName(" hello"));
100 vector<uint8_t> query;
101 DNSPacketWriter pwQ(query, a, QType::A, QClass::IN, 0);
102 pwQ.getHeader()->rd = 1;
103 uint16_t len = query.size();
104 uint32_t key = 0;
105 boost::optional<Netmask> subnet;
106 char response[4096];
107 uint16_t responseSize = sizeof(response);
108 DNSQuestion dq(&a, QType::A, QClass::IN, 0, &remote, &remote, (struct dnsheader*) query.data(), len, query.size(), false, &queryTime);
109 if(PC.get(dq, a.wirelength(), pwQ.getHeader()->id, response, &responseSize, &key, subnet)) {
110 matches++;
111 }
112 }
113 BOOST_CHECK_EQUAL(matches, expected);
114
115 PC.expungeByName(DNSName(" hello"), QType::ANY, true);
116 BOOST_CHECK_EQUAL(PC.getSize(), 0);
117 }
118 catch(PDNSException& e) {
119 cerr<<"Had error: "<<e.reason<<endl;
120 throw;
121 }
122 }
123
124 BOOST_AUTO_TEST_CASE(test_PacketCacheServFailTTL) {
125 const size_t maxEntries = 150000;
126 DNSDistPacketCache PC(maxEntries, 86400, 1);
127 struct timespec queryTime;
128 gettime(&queryTime); // does not have to be accurate ("realTime") in tests
129
130 ComboAddress remote;
131 try {
132 DNSName a = DNSName("servfail");
133 BOOST_CHECK_EQUAL(DNSName(a.toString()), a);
134
135 vector<uint8_t> query;
136 DNSPacketWriter pwQ(query, a, QType::A, QClass::IN, 0);
137 pwQ.getHeader()->rd = 1;
138
139 vector<uint8_t> response;
140 DNSPacketWriter pwR(response, a, QType::A, QClass::IN, 0);
141 pwR.getHeader()->rd = 1;
142 pwR.getHeader()->ra = 0;
143 pwR.getHeader()->qr = 1;
144 pwR.getHeader()->rcode = RCode::ServFail;
145 pwR.getHeader()->id = pwQ.getHeader()->id;
146 pwR.commit();
147 uint16_t responseLen = response.size();
148
149 char responseBuf[4096];
150 uint16_t responseBufSize = sizeof(responseBuf);
151 uint32_t key = 0;
152 boost::optional<Netmask> subnet;
153 auto dh = reinterpret_cast<dnsheader*>(query.data());
154 DNSQuestion dq(&a, QType::A, QClass::IN, 0, &remote, &remote, dh, query.size(), query.size(), false, &queryTime);
155 bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet);
156 BOOST_CHECK_EQUAL(found, false);
157 BOOST_CHECK(!subnet);
158
159 // Insert with failure-TTL of 0 (-> should not enter cache).
160 PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, RCode::ServFail, boost::optional<uint32_t>(0));
161 found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true);
162 BOOST_CHECK_EQUAL(found, false);
163 BOOST_CHECK(!subnet);
164
165 // Insert with failure-TTL non-zero (-> should enter cache).
166 PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, RCode::ServFail, boost::optional<uint32_t>(300));
167 found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true);
168 BOOST_CHECK_EQUAL(found, true);
169 BOOST_CHECK(!subnet);
170 }
171 catch(PDNSException& e) {
172 cerr<<"Had error: "<<e.reason<<endl;
173 throw;
174 }
175 }
176
177 BOOST_AUTO_TEST_CASE(test_PacketCacheNoDataTTL) {
178 const size_t maxEntries = 150000;
179 DNSDistPacketCache PC(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
180
181 struct timespec queryTime;
182 gettime(&queryTime); // does not have to be accurate ("realTime") in tests
183
184 ComboAddress remote;
185 try {
186 DNSName name("nodata");
187 vector<uint8_t> query;
188 DNSPacketWriter pwQ(query, name, QType::A, QClass::IN, 0);
189 pwQ.getHeader()->rd = 1;
190
191 vector<uint8_t> response;
192 DNSPacketWriter pwR(response, name, QType::A, QClass::IN, 0);
193 pwR.getHeader()->rd = 1;
194 pwR.getHeader()->ra = 0;
195 pwR.getHeader()->qr = 1;
196 pwR.getHeader()->rcode = RCode::NoError;
197 pwR.getHeader()->id = pwQ.getHeader()->id;
198 pwR.commit();
199 pwR.startRecord(name, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
200 pwR.commit();
201 pwR.addOpt(4096, 0, 0);
202 pwR.commit();
203
204 uint16_t responseLen = response.size();
205
206 char responseBuf[4096];
207 uint16_t responseBufSize = sizeof(responseBuf);
208 uint32_t key = 0;
209 boost::optional<Netmask> subnet;
210 auto dh = reinterpret_cast<dnsheader*>(query.data());
211 DNSQuestion dq(&name, QType::A, QClass::IN, 0, &remote, &remote, dh, query.size(), query.size(), false, &queryTime);
212 bool found = PC.get(dq, name.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet);
213 BOOST_CHECK_EQUAL(found, false);
214 BOOST_CHECK(!subnet);
215
216 PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), name, QType::A, QClass::IN, reinterpret_cast<const char*>(response.data()), responseLen, false, RCode::NoError, boost::none);
217 found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true);
218 BOOST_CHECK_EQUAL(found, true);
219 BOOST_CHECK(!subnet);
220
221 sleep(2);
222 /* it should have expired by now */
223 found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true);
224 BOOST_CHECK_EQUAL(found, false);
225 BOOST_CHECK(!subnet);
226 }
227 catch(const PDNSException& e) {
228 cerr<<"Had error: "<<e.reason<<endl;
229 throw;
230 }
231 }
232
233 BOOST_AUTO_TEST_CASE(test_PacketCacheNXDomainTTL) {
234 const size_t maxEntries = 150000;
235 DNSDistPacketCache PC(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
236
237 struct timespec queryTime;
238 gettime(&queryTime); // does not have to be accurate ("realTime") in tests
239
240 ComboAddress remote;
241 try {
242 DNSName name("nxdomain");
243 vector<uint8_t> query;
244 DNSPacketWriter pwQ(query, name, QType::A, QClass::IN, 0);
245 pwQ.getHeader()->rd = 1;
246
247 vector<uint8_t> response;
248 DNSPacketWriter pwR(response, name, QType::A, QClass::IN, 0);
249 pwR.getHeader()->rd = 1;
250 pwR.getHeader()->ra = 0;
251 pwR.getHeader()->qr = 1;
252 pwR.getHeader()->rcode = RCode::NXDomain;
253 pwR.getHeader()->id = pwQ.getHeader()->id;
254 pwR.commit();
255 pwR.startRecord(name, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
256 pwR.commit();
257 pwR.addOpt(4096, 0, 0);
258 pwR.commit();
259
260 uint16_t responseLen = response.size();
261
262 char responseBuf[4096];
263 uint16_t responseBufSize = sizeof(responseBuf);
264 uint32_t key = 0;
265 boost::optional<Netmask> subnet;
266 auto dh = reinterpret_cast<dnsheader*>(query.data());
267 DNSQuestion dq(&name, QType::A, QClass::IN, 0, &remote, &remote, dh, query.size(), query.size(), false, &queryTime);
268 bool found = PC.get(dq, name.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet);
269 BOOST_CHECK_EQUAL(found, false);
270 BOOST_CHECK(!subnet);
271
272 PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), name, QType::A, QClass::IN, reinterpret_cast<const char*>(response.data()), responseLen, false, RCode::NXDomain, boost::none);
273 found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true);
274 BOOST_CHECK_EQUAL(found, true);
275 BOOST_CHECK(!subnet);
276
277 sleep(2);
278 /* it should have expired by now */
279 found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true);
280 BOOST_CHECK_EQUAL(found, false);
281 BOOST_CHECK(!subnet);
282 }
283 catch(const PDNSException& e) {
284 cerr<<"Had error: "<<e.reason<<endl;
285 throw;
286 }
287 }
288
289 static DNSDistPacketCache PC(500000);
290
291 static void *threadMangler(void* off)
292 {
293 struct timespec queryTime;
294 gettime(&queryTime); // does not have to be accurate ("realTime") in tests
295 try {
296 ComboAddress remote;
297 unsigned int offset=(unsigned int)(unsigned long)off;
298 for(unsigned int counter=0; counter < 100000; ++counter) {
299 DNSName a=DNSName("hello ")+DNSName(std::to_string(counter+offset));
300 vector<uint8_t> query;
301 DNSPacketWriter pwQ(query, a, QType::A, QClass::IN, 0);
302 pwQ.getHeader()->rd = 1;
303
304 vector<uint8_t> response;
305 DNSPacketWriter pwR(response, a, QType::A, QClass::IN, 0);
306 pwR.getHeader()->rd = 1;
307 pwR.getHeader()->ra = 1;
308 pwR.getHeader()->qr = 1;
309 pwR.getHeader()->id = pwQ.getHeader()->id;
310 pwR.startRecord(a, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER);
311 pwR.xfr32BitInt(0x01020304);
312 pwR.commit();
313 uint16_t responseLen = response.size();
314
315 char responseBuf[4096];
316 uint16_t responseBufSize = sizeof(responseBuf);
317 uint32_t key = 0;
318 boost::optional<Netmask> subnet;
319 auto dh = reinterpret_cast<dnsheader*>(query.data());
320 DNSQuestion dq(&a, QType::A, QClass::IN, 0, &remote, &remote, dh, query.size(), query.size(), false, &queryTime);
321 PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet);
322
323 PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, 0, boost::none);
324 }
325 }
326 catch(PDNSException& e) {
327 cerr<<"Had error: "<<e.reason<<endl;
328 throw;
329 }
330 return 0;
331 }
332
333 AtomicCounter g_missing;
334
335 static void *threadReader(void* off)
336 {
337 struct timespec queryTime;
338 gettime(&queryTime); // does not have to be accurate ("realTime") in tests
339 try
340 {
341 unsigned int offset=(unsigned int)(unsigned long)off;
342 vector<DNSResourceRecord> entry;
343 ComboAddress remote;
344 for(unsigned int counter=0; counter < 100000; ++counter) {
345 DNSName a=DNSName("hello ")+DNSName(std::to_string(counter+offset));
346 vector<uint8_t> query;
347 DNSPacketWriter pwQ(query, a, QType::A, QClass::IN, 0);
348 pwQ.getHeader()->rd = 1;
349
350 char responseBuf[4096];
351 uint16_t responseBufSize = sizeof(responseBuf);
352 uint32_t key = 0;
353 boost::optional<Netmask> subnet;
354 DNSQuestion dq(&a, QType::A, QClass::IN, 0, &remote, &remote, (struct dnsheader*) query.data(), query.size(), query.size(), false, &queryTime);
355 bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet);
356 if (!found) {
357 g_missing++;
358 }
359 }
360 }
361 catch(PDNSException& e) {
362 cerr<<"Had error in threadReader: "<<e.reason<<endl;
363 throw;
364 }
365 return 0;
366 }
367
368 BOOST_AUTO_TEST_CASE(test_PacketCacheThreaded) {
369 try {
370 pthread_t tid[4];
371 for(int i=0; i < 4; ++i)
372 pthread_create(&tid[i], 0, threadMangler, (void*)(i*1000000UL));
373 void* res;
374 for(int i=0; i < 4 ; ++i)
375 pthread_join(tid[i], &res);
376
377 BOOST_CHECK_EQUAL(PC.getSize() + PC.getDeferredInserts() + PC.getInsertCollisions(), 400000);
378 BOOST_CHECK_SMALL(1.0*PC.getInsertCollisions(), 10000.0);
379
380 for(int i=0; i < 4; ++i)
381 pthread_create(&tid[i], 0, threadReader, (void*)(i*1000000UL));
382 for(int i=0; i < 4 ; ++i)
383 pthread_join(tid[i], &res);
384
385 BOOST_CHECK((PC.getDeferredInserts() + PC.getDeferredLookups() + PC.getInsertCollisions()) >= g_missing);
386 }
387 catch(PDNSException& e) {
388 cerr<<"Had error: "<<e.reason<<endl;
389 throw;
390 }
391
392 }
393
394 BOOST_AUTO_TEST_CASE(test_PCCollision) {
395 const size_t maxEntries = 150000;
396 DNSDistPacketCache PC(maxEntries, 86400, 1, 60, 3600, 60, false, 1, true, true);
397 BOOST_CHECK_EQUAL(PC.getSize(), 0);
398
399 DNSName qname("www.powerdns.com.");
400 uint16_t qtype = QType::AAAA;
401 uint16_t qid = 0x42;
402 uint32_t key;
403 uint32_t secondKey;
404 boost::optional<Netmask> subnetOut;
405
406 /* lookup for a query with an ECS value of 10.0.118.46/32,
407 insert a corresponding response */
408 {
409 vector<uint8_t> query;
410 DNSPacketWriter pwQ(query, qname, qtype, QClass::IN, 0);
411 pwQ.getHeader()->rd = 1;
412 pwQ.getHeader()->id = qid;
413 DNSPacketWriter::optvect_t ednsOptions;
414 EDNSSubnetOpts opt;
415 opt.source = Netmask("10.0.118.46/32");
416 ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
417 pwQ.addOpt(512, 0, 0, ednsOptions);
418 pwQ.commit();
419
420 char responseBuf[4096];
421 uint16_t responseBufSize = sizeof(responseBuf);
422 ComboAddress remote("192.0.2.1");
423 struct timespec queryTime;
424 gettime(&queryTime);
425 DNSQuestion dq(&qname, QType::AAAA, QClass::IN, 0, &remote, &remote, pwQ.getHeader(), query.size(), query.size(), false, &queryTime);
426 bool found = PC.get(dq, qname.wirelength(), 0, responseBuf, &responseBufSize, &key, subnetOut);
427 BOOST_CHECK_EQUAL(found, false);
428 BOOST_REQUIRE(subnetOut);
429 BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
430
431 vector<uint8_t> response;
432 DNSPacketWriter pwR(response, qname, qtype, QClass::IN, 0);
433 pwR.getHeader()->rd = 1;
434 pwR.getHeader()->id = qid;
435 pwR.startRecord(qname, qtype, 100, QClass::IN, DNSResourceRecord::ANSWER);
436 ComboAddress v6("::1");
437 pwR.xfrCAWithoutPort(6, v6);
438 pwR.commit();
439 pwR.addOpt(512, 0, 0, ednsOptions);
440 pwR.commit();
441
442 PC.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), qname, qtype, QClass::IN, reinterpret_cast<const char*>(response.data()), response.size(), false, RCode::NoError, boost::none);
443 BOOST_CHECK_EQUAL(PC.getSize(), 1);
444
445 found = PC.get(dq, qname.wirelength(), 0, responseBuf, &responseBufSize, &key, subnetOut);
446 BOOST_CHECK_EQUAL(found, true);
447 BOOST_REQUIRE(subnetOut);
448 BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
449 }
450
451 /* now lookup for the same query with an ECS value of 10.0.123.193/32
452 we should get the same key (collision) but no match */
453 {
454 vector<uint8_t> query;
455 DNSPacketWriter pwQ(query, qname, qtype, QClass::IN, 0);
456 pwQ.getHeader()->rd = 1;
457 pwQ.getHeader()->id = qid;
458 DNSPacketWriter::optvect_t ednsOptions;
459 EDNSSubnetOpts opt;
460 opt.source = Netmask("10.0.123.193/32");
461 ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
462 pwQ.addOpt(512, 0, 0, ednsOptions);
463 pwQ.commit();
464
465 char responseBuf[4096];
466 uint16_t responseBufSize = sizeof(responseBuf);
467 ComboAddress remote("192.0.2.1");
468 struct timespec queryTime;
469 gettime(&queryTime);
470 DNSQuestion dq(&qname, QType::AAAA, QClass::IN, 0, &remote, &remote, pwQ.getHeader(), query.size(), query.size(), false, &queryTime);
471 bool found = PC.get(dq, qname.wirelength(), 0, responseBuf, &responseBufSize, &secondKey, subnetOut);
472 BOOST_CHECK_EQUAL(found, false);
473 BOOST_CHECK_EQUAL(secondKey, key);
474 BOOST_REQUIRE(subnetOut);
475 BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
476 BOOST_CHECK_EQUAL(PC.getLookupCollisions(), 1);
477 }
478 }
479
480 BOOST_AUTO_TEST_SUITE_END()