1 #ifndef BOOST_TEST_DYN_LINK
2 #define BOOST_TEST_DYN_LINK
5 #define BOOST_TEST_NO_MAIN
10 #include <boost/test/unit_test.hpp>
12 #include "dnswriter.hh"
13 #include "dnsrecords.hh"
14 #include "ednscookies.hh"
15 #include "ednssubnet.hh"
16 #include "packetcache.hh"
18 BOOST_AUTO_TEST_SUITE(packetcache_hh
)
20 BOOST_AUTO_TEST_CASE(test_PacketCacheAuthCollision
) {
22 /* auth version (ECS is not processed, we just hash the whole query except for the ID, while lowercasing the qname) */
23 const DNSName
qname("www.powerdns.com.");
24 uint16_t qtype
= QType::AAAA
;
26 DNSPacketWriter::optvect_t ednsOptions
;
27 static const std::unordered_set
<uint16_t> optionsToSkip
{ EDNSOptionCode::COOKIE
};
28 static const std::unordered_set
<uint16_t> noOptionsToSkip
{ };
31 /* same query, different IDs */
32 vector
<uint8_t> packet
;
33 DNSPacketWriter
pw1(packet
, qname
, qtype
);
34 pw1
.getHeader()->rd
= true;
35 pw1
.getHeader()->qr
= false;
36 pw1
.getHeader()->id
= 0x42;
37 string
spacket1((const char*)&packet
[0], packet
.size());
38 auto hash1
= PacketCache::canHashPacket(spacket1
, optionsToSkip
);
41 DNSPacketWriter
pw2(packet
, qname
, qtype
);
42 pw2
.getHeader()->rd
= true;
43 pw2
.getHeader()->qr
= false;
44 pw2
.getHeader()->id
= 0x84;
45 string
spacket2((const char*)&packet
[0], packet
.size());
46 auto hash2
= PacketCache::canHashPacket(spacket2
, optionsToSkip
);
48 BOOST_CHECK_EQUAL(hash1
, hash2
);
49 BOOST_CHECK(PacketCache::queryMatches(spacket1
, spacket2
, qname
, optionsToSkip
));
53 /* same query, different IDs, different ECS, still hashes to the same value */
54 vector
<uint8_t> packet
;
55 DNSPacketWriter
pw1(packet
, qname
, qtype
);
56 pw1
.getHeader()->rd
= true;
57 pw1
.getHeader()->qr
= false;
58 pw1
.getHeader()->id
= 0x42;
59 opt
.source
= Netmask("10.0.59.220/32");
61 ednsOptions
.emplace_back(EDNSOptionCode::ECS
, makeEDNSSubnetOptsString(opt
));
62 pw1
.addOpt(512, 0, 0, ednsOptions
);
65 string
spacket1((const char*)&packet
[0], packet
.size());
66 auto hash1
= PacketCache::canHashPacket(spacket1
, optionsToSkip
);
69 DNSPacketWriter
pw2(packet
, qname
, qtype
);
70 pw2
.getHeader()->rd
= true;
71 pw2
.getHeader()->qr
= false;
72 pw2
.getHeader()->id
= 0x84;
73 opt
.source
= Netmask("10.0.167.48/32");
75 ednsOptions
.emplace_back(EDNSOptionCode::ECS
, makeEDNSSubnetOptsString(opt
));
76 pw2
.addOpt(512, 0, 0, ednsOptions
);
79 string
spacket2((const char*)&packet
[0], packet
.size());
80 auto hash2
= PacketCache::canHashPacket(spacket2
, optionsToSkip
);
82 BOOST_CHECK_EQUAL(hash1
, hash2
);
83 /* the hash is the same but we should _not_ match */
84 BOOST_CHECK(!PacketCache::queryMatches(spacket1
, spacket2
, qname
, optionsToSkip
));
87 /* to be able to compute a new collision if the hashing function is updated */
89 std::map
<uint32_t, Netmask
> colMap
;
90 size_t collisions
= 0;
93 for (size_t idxA
= 0; idxA
< 256; idxA
++) {
94 for (size_t idxB
= 0; idxB
< 256; idxB
++) {
95 for (size_t idxC
= 0; idxC
< 256; idxC
++) {
96 vector
<uint8_t> secondQuery
;
97 DNSPacketWriter
pwFQ(secondQuery
, qname
, QType::AAAA
, QClass::IN
, 0);
98 pwFQ
.getHeader()->rd
= 1;
99 pwFQ
.getHeader()->qr
= false;
100 pwFQ
.getHeader()->id
= 0x42;
101 opt
.source
= Netmask("10." + std::to_string(idxA
) + "." + std::to_string(idxB
) + "." + std::to_string(idxC
) + "/32");
103 ednsOptions
.emplace_back(EDNSOptionCode::ECS
, makeEDNSSubnetOptsString(opt
));
104 pwFQ
.addOpt(512, 0, 0, ednsOptions
);
106 auto secondKey
= PacketCache::canHashPacket(std::string(reinterpret_cast<const char *>(secondQuery
.data()), secondQuery
.size()), optionsToSkip
);
107 auto pair
= colMap
.emplace(secondKey
, opt
.source
);
111 cerr
<<"Collision between "<<colMap
[secondKey
].toString()<<" and "<<opt
.source
.toString()<<" for key "<<secondKey
<<endl
;
118 cerr
<<"collisions: "<<collisions
<<endl
;
119 cerr
<<"total: "<<total
<<endl
;
125 /* same query but one has DNSSECOK, not the other, different IDs, different ECS, still hashes to the same value */
126 vector
<uint8_t> packet
;
127 DNSPacketWriter
pw1(packet
, qname
, qtype
);
128 pw1
.getHeader()->rd
= true;
129 pw1
.getHeader()->qr
= false;
130 pw1
.getHeader()->id
= 0x42;
131 opt
.source
= Netmask("10.0.41.6/32");
133 ednsOptions
.emplace_back(EDNSOptionCode::ECS
, makeEDNSSubnetOptsString(opt
));
134 pw1
.addOpt(512, 0, EDNSOpts::DNSSECOK
, ednsOptions
);
137 string
spacket1((const char*)&packet
[0], packet
.size());
138 auto hash1
= PacketCache::canHashPacket(spacket1
, optionsToSkip
);
141 DNSPacketWriter
pw2(packet
, qname
, qtype
);
142 pw2
.getHeader()->rd
= true;
143 pw2
.getHeader()->qr
= false;
144 pw2
.getHeader()->id
= 0x84;
145 opt
.source
= Netmask("10.0.119.79/32");
147 ednsOptions
.emplace_back(EDNSOptionCode::ECS
, makeEDNSSubnetOptsString(opt
));
148 /* no EDNSOpts::DNSSECOK !! */
149 pw2
.addOpt(512, 0, 0, ednsOptions
);
152 string
spacket2((const char*)&packet
[0], packet
.size());
153 auto hash2
= PacketCache::canHashPacket(spacket2
, optionsToSkip
);
155 BOOST_CHECK_EQUAL(hash1
, hash2
);
156 /* the hash is the same but we should _not_ match */
157 BOOST_CHECK(!PacketCache::queryMatches(spacket1
, spacket2
, qname
, optionsToSkip
));
161 /* same query but different cookies, still hashes to the same value */
162 vector
<uint8_t> packet
;
163 DNSPacketWriter
pw1(packet
, qname
, qtype
);
164 pw1
.getHeader()->rd
= true;
165 pw1
.getHeader()->qr
= false;
166 pw1
.getHeader()->id
= 0x42;
167 opt
.source
= Netmask("192.0.2.1/32");
169 ednsOptions
.emplace_back(EDNSOptionCode::ECS
, makeEDNSSubnetOptsString(opt
));
170 EDNSCookiesOpt
cookiesOpt(string("deadbeefdeadbeef"));
171 ednsOptions
.emplace_back(EDNSOptionCode::COOKIE
, cookiesOpt
.makeOptString());
172 pw1
.addOpt(512, 0, EDNSOpts::DNSSECOK
, ednsOptions
);
175 string
spacket1((const char*)&packet
[0], packet
.size());
176 auto hash1
= PacketCache::canHashPacket(spacket1
, optionsToSkip
);
179 DNSPacketWriter
pw2(packet
, qname
, qtype
);
180 pw2
.getHeader()->rd
= true;
181 pw2
.getHeader()->qr
= false;
182 pw2
.getHeader()->id
= 0x84;
183 opt
.source
= Netmask("192.0.2.1/32");
185 ednsOptions
.emplace_back(EDNSOptionCode::ECS
, makeEDNSSubnetOptsString(opt
));
186 cookiesOpt
.makeFromString(string("deadbeefbadc0fee"));
187 ednsOptions
.emplace_back(EDNSOptionCode::COOKIE
, cookiesOpt
.makeOptString());
188 pw2
.addOpt(512, 0, EDNSOpts::DNSSECOK
, ednsOptions
);
191 string
spacket2((const char*)&packet
[0], packet
.size());
192 auto hash2
= PacketCache::canHashPacket(spacket2
, optionsToSkip
);
194 BOOST_CHECK_EQUAL(hash1
, hash2
);
195 /* the hash is the same but we should _not_ match */
196 BOOST_CHECK(!PacketCache::queryMatches(spacket1
, spacket2
, qname
, noOptionsToSkip
));
197 /* but it does match if we skip cookies, though */
198 BOOST_CHECK(PacketCache::queryMatches(spacket1
, spacket2
, qname
, optionsToSkip
));
202 /* to be able to compute a new collision if the packet cache hashing code is updated */
203 std::map
<uint32_t, Netmask
> colMap
;
204 size_t collisions
= 0;
207 for (size_t idxA
= 0; idxA
< 256; idxA
++) {
208 for (size_t idxB
= 0; idxB
< 256; idxB
++) {
209 for (size_t idxC
= 0; idxC
< 256; idxC
++) {
210 vector
<uint8_t> secondQuery
;
211 DNSPacketWriter
pwFQ(secondQuery
, qname
, QType::AAAA
, QClass::IN
, 0);
212 pwFQ
.getHeader()->rd
= 1;
213 pwFQ
.getHeader()->qr
= false;
214 pwFQ
.getHeader()->id
= 0x42;
215 opt
.source
= Netmask("10." + std::to_string(idxA
) + "." + std::to_string(idxB
) + "." + std::to_string(idxC
) + "/32");
217 ednsOptions
.emplace_back(EDNSOptionCode::ECS
, makeEDNSSubnetOptsString(opt
));
218 pwFQ
.addOpt(512, 0, 32768, ednsOptions
);
220 auto secondKey
= PacketCache::canHashPacket(std::string(reinterpret_cast<const char *>(secondQuery
.data()), secondQuery
.size()), optionsToSkip
);
221 colMap
.emplace(secondKey
, opt
.source
);
224 DNSPacketWriter
pwSQ(secondQuery
, qname
, QType::AAAA
, QClass::IN
, 0);
225 pwSQ
.getHeader()->rd
= 1;
226 pwSQ
.getHeader()->qr
= false;
227 pwSQ
.getHeader()->id
= 0x42;
228 opt
.source
= Netmask("10." + std::to_string(idxA
) + "." + std::to_string(idxB
) + "." + std::to_string(idxC
) + "/32");
230 ednsOptions
.emplace_back(EDNSOptionCode::ECS
, makeEDNSSubnetOptsString(opt
));
231 pwSQ
.addOpt(512, 0, 0, ednsOptions
);
233 secondKey
= PacketCache::canHashPacket(std::string(reinterpret_cast<const char *>(secondQuery
.data()), secondQuery
.size()), optionsToSkip
);
236 if (colMap
.count(secondKey
)) {
238 cerr
<<"Collision between "<<colMap
[secondKey
].toString()<<" and "<<opt
.source
.toString()<<" for key "<<secondKey
<<endl
;
245 cerr
<<"collisions: "<<collisions
<<endl
;
246 cerr
<<"total: "<<total
<<endl
;
252 BOOST_AUTO_TEST_CASE(test_PacketCacheRecSimple
) {
254 const DNSName
qname("www.powerdns.com.");
255 uint16_t qtype
= QType::AAAA
;
257 DNSPacketWriter::optvect_t ednsOptions
;
258 static const std::unordered_set
<uint16_t> optionsToSkip
{ EDNSOptionCode::COOKIE
, EDNSOptionCode::ECS
};
261 vector
<uint8_t> packet
;
262 DNSPacketWriter
pw1(packet
, qname
, qtype
);
263 pw1
.getHeader()->rd
= true;
264 pw1
.getHeader()->qr
= false;
265 pw1
.getHeader()->id
= 0x42;
266 pw1
.addOpt(512, 0, 0);
269 string
spacket1((const char*)&packet
[0], packet
.size());
270 /* set the RD length to a large value */
271 unsigned char* ptr
= reinterpret_cast<unsigned char*>(&spacket1
.at(sizeof(dnsheader
) + qname
.wirelength() + /* qtype and qclass */ 4 + /* OPT root label (1), type (2), class (2) and ttl (4) */ 9));
274 /* truncate the end of the OPT header to try to trigger an out of bounds read */
275 spacket1
.resize(spacket1
.size() - 6);
276 BOOST_CHECK_NO_THROW(PacketCache::canHashPacket(spacket1
, optionsToSkip
));
280 BOOST_AUTO_TEST_CASE(test_PacketCacheRecCollision
) {
282 /* rec version (ECS is processed, we hash the whole query except for the ID and the ECS value, while lowercasing the qname) */
283 const DNSName
qname("www.powerdns.com.");
284 uint16_t qtype
= QType::AAAA
;
286 DNSPacketWriter::optvect_t ednsOptions
;
287 static const std::unordered_set
<uint16_t> optionsToSkip
{ EDNSOptionCode::COOKIE
, EDNSOptionCode::ECS
};
290 /* same query, different IDs */
291 vector
<uint8_t> packet
;
292 DNSPacketWriter
pw1(packet
, qname
, qtype
);
293 pw1
.getHeader()->rd
= true;
294 pw1
.getHeader()->qr
= false;
295 pw1
.getHeader()->id
= 0x42;
296 string
spacket1((const char*)&packet
[0], packet
.size());
297 auto hash1
= PacketCache::canHashPacket(spacket1
, optionsToSkip
);
300 DNSPacketWriter
pw2(packet
, qname
, qtype
);
301 pw2
.getHeader()->rd
= true;
302 pw2
.getHeader()->qr
= false;
303 pw2
.getHeader()->id
= 0x84;
304 string
spacket2((const char*)&packet
[0], packet
.size());
305 auto hash2
= PacketCache::canHashPacket(spacket2
, optionsToSkip
);
307 BOOST_CHECK_EQUAL(hash1
, hash2
);
308 BOOST_CHECK(PacketCache::queryMatches(spacket1
, spacket2
, qname
, optionsToSkip
));
312 /* same query, different IDs, different ECS, still hashes to the same value */
313 vector
<uint8_t> packet
;
314 DNSPacketWriter
pw1(packet
, qname
, qtype
);
315 pw1
.getHeader()->rd
= true;
316 pw1
.getHeader()->qr
= false;
317 pw1
.getHeader()->id
= 0x42;
318 opt
.source
= Netmask("10.0.18.199/32");
320 ednsOptions
.emplace_back(EDNSOptionCode::ECS
, makeEDNSSubnetOptsString(opt
));
321 pw1
.addOpt(512, 0, 0, ednsOptions
);
324 string
spacket1((const char*)&packet
[0], packet
.size());
325 auto hash1
= PacketCache::canHashPacket(spacket1
, optionsToSkip
);
328 DNSPacketWriter
pw2(packet
, qname
, qtype
);
329 pw2
.getHeader()->rd
= true;
330 pw2
.getHeader()->qr
= false;
331 pw2
.getHeader()->id
= 0x84;
332 opt
.source
= Netmask("10.0.131.66/32");
334 ednsOptions
.emplace_back(EDNSOptionCode::ECS
, makeEDNSSubnetOptsString(opt
));
335 pw2
.addOpt(512, 0, 0, ednsOptions
);
338 string
spacket2((const char*)&packet
[0], packet
.size());
339 auto hash2
= PacketCache::canHashPacket(spacket2
, optionsToSkip
);
341 BOOST_CHECK_EQUAL(hash1
, hash2
);
342 /* the hash is the same and we don't hash the ECS so we should match */
343 BOOST_CHECK(PacketCache::queryMatches(spacket1
, spacket2
, qname
, optionsToSkip
));
347 /* same query but different cookies, still hashes to the same value */
348 vector
<uint8_t> packet
;
349 DNSPacketWriter
pw1(packet
, qname
, qtype
);
350 pw1
.getHeader()->rd
= true;
351 pw1
.getHeader()->qr
= false;
352 pw1
.getHeader()->id
= 0x42;
353 opt
.source
= Netmask("192.0.2.1/32");
355 ednsOptions
.emplace_back(EDNSOptionCode::ECS
, makeEDNSSubnetOptsString(opt
));
356 EDNSCookiesOpt
cookiesOpt(string("deadbeefdead\x11\xee\x00\x00").c_str(), 16);
357 ednsOptions
.emplace_back(EDNSOptionCode::COOKIE
, cookiesOpt
.makeOptString());
358 pw1
.addOpt(512, 0, EDNSOpts::DNSSECOK
, ednsOptions
);
361 string
spacket1((const char*)&packet
[0], packet
.size());
362 auto hash1
= PacketCache::canHashPacket(spacket1
, optionsToSkip
);
365 DNSPacketWriter
pw2(packet
, qname
, qtype
);
366 pw2
.getHeader()->rd
= true;
367 pw2
.getHeader()->qr
= false;
368 pw2
.getHeader()->id
= 0x84;
369 opt
.source
= Netmask("192.0.2.1/32");
371 ednsOptions
.emplace_back(EDNSOptionCode::ECS
, makeEDNSSubnetOptsString(opt
));
372 cookiesOpt
.makeFromString(string("deadbeefdead\x67\x44\x00\x00").c_str(), 16);
373 ednsOptions
.emplace_back(EDNSOptionCode::COOKIE
, cookiesOpt
.makeOptString());
374 pw2
.addOpt(512, 0, EDNSOpts::DNSSECOK
, ednsOptions
);
377 string
spacket2((const char*)&packet
[0], packet
.size());
378 auto hash2
= PacketCache::canHashPacket(spacket2
, optionsToSkip
);
380 BOOST_CHECK_EQUAL(hash1
, hash2
);
381 /* the hash is the same but we should _not_ match, even though we skip the ECS part, because the cookies are different */
382 static const std::unordered_set
<uint16_t> skipECSOnly
{ EDNSOptionCode::ECS
};
383 BOOST_CHECK(!PacketCache::queryMatches(spacket1
, spacket2
, qname
, skipECSOnly
));
385 /* we do match if we skip the cookie as well */
386 BOOST_CHECK(PacketCache::queryMatches(spacket1
, spacket2
, qname
, optionsToSkip
));
390 BOOST_AUTO_TEST_SUITE_END()